Auth: Add unique index to user_slug in auth_users table #98

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2022-09-02 22:55:57 +02:00
parent 85561547cc
commit 5e7ff6b1b2
10 changed files with 31 additions and 35 deletions

View file

@ -14,7 +14,6 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/clean"
)
// ResetCommand resets the index, clears the cache, and removes sidecar files after confirmation.
@ -181,8 +180,8 @@ func resetIndexDb(c *config.Config) {
// Reset admin account?
if c.AdminPassword() == "" {
log.Warnf("password required to reset admin account")
} else if entity.Admin.InitAccount(c.AdminUser(), c.AdminPassword()) {
log.Infof("user %s has been restored", clean.LogQuote(c.AdminUser()))
} else {
entity.Admin.InitAccount(c.AdminUser(), c.AdminPassword())
}
log.Infof("database reset completed in %s", time.Since(start))

View file

@ -275,8 +275,8 @@ func (c *Config) MigrateDb(runFailed bool, ids []string) {
// Init admin account?
if c.AdminPassword() == "" {
log.Warnf("config: password required to initialize %s account", clean.LogQuote(c.AdminUser()))
} else if entity.Admin.InitAccount(c.AdminUser(), c.AdminPassword()) {
log.Infof("config: %s account has been initialized", clean.LogQuote(c.AdminUser()))
} else {
entity.Admin.InitAccount(c.AdminUser(), c.AdminPassword())
}
go entity.SaveErrorMessages()
@ -290,8 +290,8 @@ func (c *Config) InitTestDb() {
if c.AdminPassword() == "" {
// Do nothing.
} else if entity.Admin.InitAccount(c.AdminUser(), c.AdminPassword()) {
log.Debugf("config: %s account has been initialized", clean.LogQuote(c.AdminUser()))
} else {
entity.Admin.InitAccount(c.AdminUser(), c.AdminPassword())
}
go entity.SaveErrorMessages()

View file

@ -25,7 +25,7 @@ type Users []User
type User struct {
ID int `gorm:"primary_key" json:"-" yaml:"-"`
UserUID string `gorm:"type:VARBINARY(42);unique_index;" json:"UID" yaml:"UID"`
UserSlug string `gorm:"type:VARBINARY(160);index;" json:"Slug" yaml:"Slug,omitempty"`
UserSlug string `gorm:"type:VARBINARY(160);unique_index;" json:"Slug" yaml:"Slug,omitempty"`
Username string `gorm:"size:64;index;" json:"Username" yaml:"Username,omitempty"`
Email string `gorm:"size:255;index;" json:"Email" yaml:"Email,omitempty"`
UserRole string `gorm:"size:32;" json:"Role" yaml:"Role,omitempty"`
@ -102,12 +102,6 @@ func (m *User) InitAccount(login, password string) (updated bool) {
return false
}
// Update username as well if needed.
if err := m.UpdateName(login); err != nil {
log.Errorf("user: %s", err.Error())
return false
}
existing := FindPassword(m.UserUID)
if existing != nil {
@ -116,12 +110,17 @@ func (m *User) InitAccount(login, password string) (updated bool) {
pw := NewPassword(m.UserUID, password)
// Update password in database.
// Save password.
if err := pw.Save(); err != nil {
log.Error(err)
return false
}
// Change username.
if err := m.UpdateName(login); err != nil {
log.Debugf("auth: cannot change username of %s to %s (%s)", clean.Log(m.UserUID), clean.LogQuote(login), err.Error())
}
return true
}
@ -174,7 +173,7 @@ func FirstOrCreateUser(m *User) *User {
m.UserSlug = m.GenerateSlug()
if err := Db().Where("id = ? OR user_uid = ?", m.ID, m.UserUID).First(&result).Error; err == nil {
if err := Db().Where("id = ? OR (user_uid = ? AND user_uid <> '') OR (user_slug = ? AND user_slug <> '') OR (username = ? AND username <> '')", m.ID, m.UserUID, m.UserSlug, m.Username).First(&result).Error; err == nil {
return &result
} else if err := m.Create(); err != nil {
log.Debugf("user: %s", err)
@ -307,7 +306,7 @@ func (m *User) SetUsername(login string) (err error) {
m.UserSlug = m.GenerateSlug()
// Update display name.
if m.DisplayName == "" || m.DisplayName == DefaultAdminFullName {
if m.DisplayName == "" || m.DisplayName == AdminDisplayName {
m.DisplayName = clean.Name(login)
}

View file

@ -5,19 +5,17 @@ import (
"github.com/photoprism/photoprism/pkg/rnd"
)
const DefaultAdminUserName = "admin"
const DefaultAdminFullName = "Admin"
const DisplayNameUnknown = "Public"
const DisplayNameGuest = "Guest"
const AdminUserName = "admin"
const AdminDisplayName = "Admin"
const GuestDisplayName = "Guest"
// Admin is the default admin user.
var Admin = User{
ID: 1,
UserSlug: "admin",
Username: DefaultAdminUserName,
Username: AdminUserName,
UserRole: acl.RoleAdmin.String(),
DisplayName: DefaultAdminFullName,
DisplayName: AdminDisplayName,
SuperAdmin: true,
CanLogin: true,
CanInvite: true,
@ -27,11 +25,11 @@ var Admin = User{
// UnknownUser is an anonymous, public user without own account.
var UnknownUser = User{
ID: -1,
UserSlug: "1",
UserSlug: "",
UserUID: "u000000000000001",
UserRole: "",
Username: "",
DisplayName: DisplayNameUnknown,
DisplayName: "",
SuperAdmin: false,
CanLogin: false,
CanInvite: false,
@ -41,11 +39,11 @@ var UnknownUser = User{
// Guest is a user without own account e.g. for link sharing.
var Guest = User{
ID: -2,
UserSlug: "2",
UserSlug: "guest",
UserUID: "u000000000000002",
UserRole: acl.RoleGuest.String(),
Username: "",
DisplayName: DisplayNameGuest,
Username: "guest",
DisplayName: GuestDisplayName,
SuperAdmin: false,
CanLogin: false,
CanInvite: false,

View file

@ -232,7 +232,7 @@ func TestFindUserByUID(t *testing.T) {
assert.Equal(t, -2, m.ID)
assert.NotEmpty(t, m.UserUID)
assert.Equal(t, "", m.Username)
assert.Equal(t, "guest", m.Username)
assert.Equal(t, "Guest", m.DisplayName)
assert.NotEmpty(t, m.CreatedAt)
assert.NotEmpty(t, m.UpdatedAt)

View file

@ -101,6 +101,6 @@ var DialectMySQL = Migrations{
{
ID: "20220901-000100",
Dialect: "mysql",
Statements: []string{"INSERT IGNORE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND role_admin = 1 AND user_disabled = 0;"},
Statements: []string{"REPLACE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE role_admin = 1 AND user_name NOT IN (SELECT user_slug FROM auth_users UNION SELECT user_slug FROM auth_users) AND user_name <> '' AND user_name IS NOT NULL;"},
},
}

View file

@ -61,6 +61,6 @@ var DialectSQLite3 = Migrations{
{
ID: "20220901-000100",
Dialect: "sqlite3",
Statements: []string{"INSERT OR IGNORE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, 1, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0 ON CONFLICT DO NOTHING;"},
Statements: []string{"REPLACE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, 1, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0;"},
},
}

View file

@ -111,7 +111,7 @@ func (m *Migration) Execute(db *gorm.DB) error {
} else if strings.HasPrefix(q, "DROP TABLE ") &&
strings.Contains(e, "DROP") {
log.Tracef("migrate: %s (ignored, probably didn't exist anymore)", err)
} else if strings.Contains(q, " IGNORE ") &&
} else if (strings.Contains(q, " IGNORE ") || strings.Contains(q, "REPLACE")) &&
(strings.Contains(e, "NO SUCH TABLE") || strings.Contains(e, "DOESN'T EXIST")) {
log.Tracef("migrate: %s (ignored, old table does not exist anymore)", err)
} else {

View file

@ -1 +1 @@
INSERT IGNORE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND role_admin = 1 AND user_disabled = 0;
REPLACE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, role_admin, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE role_admin = 1 AND user_name NOT IN (SELECT user_slug FROM auth_users UNION SELECT user_slug FROM auth_users) AND user_name <> '' AND user_name IS NOT NULL;

View file

@ -1 +1 @@
INSERT OR IGNORE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, 1, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0 ON CONFLICT DO NOTHING;
REPLACE INTO auth_users (id, user_uid, super_admin, user_role, display_name, user_slug, username, email, login_attempts, login_at, created_at, updated_at) SELECT id, user_uid, 1, 'admin', full_name, user_name, user_name, primary_email, login_attempts, login_at, created_at, updated_at FROM users WHERE user_name <> '' AND user_name IS NOT NULL AND user_uid <> '' AND user_uid IS NOT NULL AND role_admin = 1 AND user_disabled = 0;