From 5e7ff6b1b26762725a36bcca51b49cdcc3bf8d36 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Fri, 2 Sep 2022 22:55:57 +0200 Subject: [PATCH] Auth: Add unique index to user_slug in auth_users table #98 Signed-off-by: Michael Mayer --- internal/commands/reset.go | 5 ++--- internal/config/config_db.go | 8 +++---- internal/entity/auth_user.go | 19 ++++++++--------- internal/entity/auth_user_default.go | 22 +++++++++----------- internal/entity/auth_user_test.go | 2 +- internal/migrate/dialect_mysql.go | 2 +- internal/migrate/dialect_sqlite3.go | 2 +- internal/migrate/migration.go | 2 +- internal/migrate/mysql/20220901-000100.sql | 2 +- internal/migrate/sqlite3/20220901-000100.sql | 2 +- 10 files changed, 31 insertions(+), 35 deletions(-) diff --git a/internal/commands/reset.go b/internal/commands/reset.go index 65ebf05c1..476d1aa11 100644 --- a/internal/commands/reset.go +++ b/internal/commands/reset.go @@ -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)) diff --git a/internal/config/config_db.go b/internal/config/config_db.go index 9b1377dc6..74a0d644b 100644 --- a/internal/config/config_db.go +++ b/internal/config/config_db.go @@ -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() diff --git a/internal/entity/auth_user.go b/internal/entity/auth_user.go index ab34caf9c..c135ea2b6 100644 --- a/internal/entity/auth_user.go +++ b/internal/entity/auth_user.go @@ -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) } diff --git a/internal/entity/auth_user_default.go b/internal/entity/auth_user_default.go index 5a0a6bf1d..f55865276 100644 --- a/internal/entity/auth_user_default.go +++ b/internal/entity/auth_user_default.go @@ -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, diff --git a/internal/entity/auth_user_test.go b/internal/entity/auth_user_test.go index 8bec18cc6..875f0279b 100644 --- a/internal/entity/auth_user_test.go +++ b/internal/entity/auth_user_test.go @@ -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) diff --git a/internal/migrate/dialect_mysql.go b/internal/migrate/dialect_mysql.go index 597e45140..93aa85ea5 100644 --- a/internal/migrate/dialect_mysql.go +++ b/internal/migrate/dialect_mysql.go @@ -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;"}, }, } diff --git a/internal/migrate/dialect_sqlite3.go b/internal/migrate/dialect_sqlite3.go index 835d1f942..7299fa03f 100644 --- a/internal/migrate/dialect_sqlite3.go +++ b/internal/migrate/dialect_sqlite3.go @@ -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;"}, }, } diff --git a/internal/migrate/migration.go b/internal/migrate/migration.go index 495feb604..1dd79ba1c 100644 --- a/internal/migrate/migration.go +++ b/internal/migrate/migration.go @@ -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 { diff --git a/internal/migrate/mysql/20220901-000100.sql b/internal/migrate/mysql/20220901-000100.sql index 3ae4f18f7..5ced1bf13 100644 --- a/internal/migrate/mysql/20220901-000100.sql +++ b/internal/migrate/mysql/20220901-000100.sql @@ -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; \ No newline at end of file +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; \ No newline at end of file diff --git a/internal/migrate/sqlite3/20220901-000100.sql b/internal/migrate/sqlite3/20220901-000100.sql index 4e6a168da..9f920c476 100644 --- a/internal/migrate/sqlite3/20220901-000100.sql +++ b/internal/migrate/sqlite3/20220901-000100.sql @@ -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; \ No newline at end of file +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; \ No newline at end of file