From 80173b1b1d26c170322a07d11e46be5c42cf32f6 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 1 Jun 2026 20:48:12 +0200 Subject: [PATCH] fix(db): make password-hash migration idempotent to prevent lock-out (#4612) The UserPasswordHash seeder bcrypt-hashed user.Password unconditionally, assuming plaintext. If it ran on an already-bcrypt value (DB restore, SQLite<->Postgres switch, history_of_seeders inconsistency on upgrade) it double-hashed the password, locking the admin out with both old and new passwords rejected. Skip any password that is already a bcrypt hash. --- database/db.go | 3 +++ util/crypto/crypto.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/database/db.go b/database/db.go index 85e0eb75..c43ffb9e 100644 --- a/database/db.go +++ b/database/db.go @@ -203,6 +203,9 @@ func runSeeders(isUsersEmpty bool) error { } for _, user := range users { + if crypto.IsHashed(user.Password) { + continue + } hashedPassword, err := crypto.HashPasswordAsBcrypt(user.Password) if err != nil { log.Printf("Error hashing password for user '%s': %v", user.Username, err) diff --git a/util/crypto/crypto.go b/util/crypto/crypto.go index 2db5bd83..bd7b6ffa 100644 --- a/util/crypto/crypto.go +++ b/util/crypto/crypto.go @@ -15,3 +15,8 @@ func HashPasswordAsBcrypt(password string) (string, error) { func CheckPasswordHash(hash, password string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } + +func IsHashed(s string) bool { + _, err := bcrypt.Cost([]byte(s)) + return err == nil +}