From a4975cdb2dd644753f8caf5e9e885d9485cde4ec Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Sun, 31 May 2026 20:07:33 +0330 Subject: [PATCH] fix(seeder): patch existing admin username/password on every boot EnsureOwnerAdminAsync now sets Username='admin' (configurable via Seed:SystemAdminUsername) on any existing admin that has no username, and hashes Seed:SystemAdminPassword if provided and no hash is stored. Covers fresh deploys and existing prod admins created before credentials were added. Co-Authored-By: Claude Opus 4.7 --- .../Data/PlatformDataSeeder.cs | 64 +++++++++++++------ 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/Meezi.Infrastructure/Data/PlatformDataSeeder.cs b/src/Meezi.Infrastructure/Data/PlatformDataSeeder.cs index 8fddee4..5534019 100644 --- a/src/Meezi.Infrastructure/Data/PlatformDataSeeder.cs +++ b/src/Meezi.Infrastructure/Data/PlatformDataSeeder.cs @@ -54,9 +54,11 @@ public static class PlatformDataSeeder private static async Task EnsureOwnerAdminAsync(AppDbContext db, IConfiguration config, ILogger logger) { const string DefaultOwnerPhone = "09190345606"; - var configured = config["Seed:SystemAdminPhone"]; + const string DefaultAdminUsername = "admin"; + + var configuredPhone = config["Seed:SystemAdminPhone"]; var phone = PhoneNormalizer.Normalize( - string.IsNullOrWhiteSpace(configured) ? DefaultOwnerPhone : configured); + string.IsNullOrWhiteSpace(configuredPhone) ? DefaultOwnerPhone : configuredPhone); if (!PhoneNormalizer.IsValidIranMobile(phone)) { @@ -64,27 +66,53 @@ public static class PlatformDataSeeder return; } - if (await db.SystemAdmins.AnyAsync(a => a.Phone == phone)) - return; + var configuredUsername = config["Seed:SystemAdminUsername"]; + var username = string.IsNullOrWhiteSpace(configuredUsername) ? DefaultAdminUsername : configuredUsername.Trim().ToLowerInvariant(); + var defaultPassword = config["Seed:SystemAdminPassword"]; // optional — only set if provided - db.SystemAdmins.Add(new SystemAdmin + var existing = await db.SystemAdmins.FirstOrDefaultAsync(a => a.Phone == phone); + + if (existing is null) { - Id = "sysadmin_owner", - Name = "مدیر سامانه", - Phone = phone, - IsActive = true - }); + var admin = new SystemAdmin + { + Id = "sysadmin_owner", + Name = "مدیر سامانه", + Phone = phone, + IsActive = true, + Username = username, + PasswordHash = string.IsNullOrWhiteSpace(defaultPassword) ? null : PasswordHasher.Hash(defaultPassword) + }; + db.SystemAdmins.Add(admin); - try + try + { + await db.SaveChangesAsync(); + logger.LogInformation("Seeded owner system admin with phone {Phone}, username '{Username}'", phone, username); + } + catch (DbUpdateException) + { + logger.LogInformation("Owner system admin already seeded by another instance"); + } + return; + } + + // Patch existing admin: fill in missing username / password without overwriting set values + var patched = false; + if (string.IsNullOrWhiteSpace(existing.Username)) + { + existing.Username = username; + patched = true; + } + if (string.IsNullOrWhiteSpace(existing.PasswordHash) && !string.IsNullOrWhiteSpace(defaultPassword)) + { + existing.PasswordHash = PasswordHasher.Hash(defaultPassword); + patched = true; + } + if (patched) { await db.SaveChangesAsync(); - logger.LogInformation("Seeded owner system admin with phone {Phone}", phone); - } - catch (DbUpdateException) - { - // api + admin-api boot concurrently against the same DB; another instance - // already inserted this admin. Safe to ignore. - logger.LogInformation("Owner system admin already seeded by another instance"); + logger.LogInformation("Patched owner system admin credentials (username/password)"); } }