fix(09): WR-11 enforce case-insensitive unique backend user emails
Add a backend admin migration that creates a unique index on lower(backend_users.email). Rows copied from WinterCMS may hold emails that differ only in case, so the migration refuses to run and names the clashing logins instead of choosing an account to drop.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package lagoon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -9,10 +12,11 @@ import (
|
||||
// It is distinct from the frontend jwt_blacklist table.
|
||||
const BackendJWTBlacklistTable = "backend_jwt_blacklist"
|
||||
|
||||
// BackendAdminMigrations creates Winter-shaped backend identity tables and
|
||||
// seeds the developer and publisher system roles. History is isolated under
|
||||
// the summercms.cabana plugin id. DDL is re-runnable so the system-role seed
|
||||
// stays idempotent if the history row is removed.
|
||||
// BackendAdminMigrations creates Winter-shaped backend identity tables, seeds
|
||||
// the developer and publisher system roles and makes backend user emails
|
||||
// unique case-insensitively. History is isolated under the summercms.cabana
|
||||
// plugin id. DDL is re-runnable so the system-role seed stays idempotent if
|
||||
// the history row is removed.
|
||||
var BackendAdminMigrations = []*gormigrate.Migration{
|
||||
{
|
||||
ID: "202609240001_backend_admin_identity",
|
||||
@@ -85,4 +89,32 @@ ON CONFLICT (name) DO NOTHING`,
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
// Emails are matched case-insensitively at login and by admin:create,
|
||||
// so the table enforces the same rule. Rows copied from Winter may
|
||||
// hold two emails that differ only in case; the migration refuses to
|
||||
// run and names them instead of picking an account to drop.
|
||||
ID: "202610010001_backend_users_email_ci_unique",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var dupes []struct {
|
||||
Email string
|
||||
Logins string
|
||||
}
|
||||
if err := tx.Raw(`SELECT lower(email) AS email, string_agg(login, ', ' ORDER BY id) AS logins
|
||||
FROM backend_users GROUP BY lower(email) HAVING count(*) > 1 ORDER BY 1`).Scan(&dupes).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dupes) > 0 {
|
||||
parts := make([]string, len(dupes))
|
||||
for i, d := range dupes {
|
||||
parts[i] = fmt.Sprintf("%s (logins: %s)", d.Email, d.Logins)
|
||||
}
|
||||
return fmt.Errorf("lagoon: backend_users has emails that differ only in case: %s; change or remove the duplicates, then run migrate again", strings.Join(parts, "; "))
|
||||
}
|
||||
return tx.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS backend_users_email_lower_unique ON backend_users (lower(email))`).Error
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return tx.Exec(`DROP INDEX IF EXISTS backend_users_email_lower_unique`).Error
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user