fix(seeder): patch existing admin username/password on every boot
CI/CD / CI · API (dotnet build + test) (push) Has been cancelled
CI/CD / CI · Admin API (dotnet build) (push) Has been cancelled
CI/CD / CI · Dashboard (tsc) (push) Has been cancelled
CI/CD / CI · Admin Web (tsc) (push) Has been cancelled
CI/CD / CI · Website (tsc) (push) Has been cancelled
CI/CD / CI · Koja (tsc) (push) Has been cancelled
CI/CD / Deploy · all services (push) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-31 20:07:33 +03:30
parent 639d5c305e
commit a4975cdb2d
@@ -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,28 +66,54 @@ 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)
{
var admin = new SystemAdmin
{
Id = "sysadmin_owner",
Name = "مدیر سامانه",
Phone = phone,
IsActive = true
});
IsActive = true,
Username = username,
PasswordHash = string.IsNullOrWhiteSpace(defaultPassword) ? null : PasswordHasher.Hash(defaultPassword)
};
db.SystemAdmins.Add(admin);
try
{
await db.SaveChangesAsync();
logger.LogInformation("Seeded owner system admin with phone {Phone}", phone);
logger.LogInformation("Seeded owner system admin with phone {Phone}, username '{Username}'", phone, username);
}
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");
}
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("Patched owner system admin credentials (username/password)");
}
}
/// <summary>Idempotent plan/feature upgrades for all environments (including production).</summary>