Seed personal team accounts

This commit is contained in:
AzuTear
2026-06-25 20:06:48 +02:00
parent e2f31d99fd
commit 3bf8b00a83
2 changed files with 153 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
using Backend.Domain;
using Backend.Security;
using Microsoft.EntityFrameworkCore;
namespace Backend.Data;
public static class TeamAccountBootstrapper
{
private const string SeedActor = "system:team-seed";
public static async Task EnsureAsync(AwardsDbContext db, IConfiguration configuration)
{
var ownerSeed = BuildSeed(
configuration,
sectionName: "Owner",
environmentPrefix: "VTSA_TEAM_OWNER",
defaultLogin: "jayuhime",
defaultDisplayName: "Jayuhime",
role: AdminRoles.Owner,
fallbackPassword: configuration["VTSA_DEMO_ADMIN_PASSWORD"] ?? configuration["DemoAdmin:Password"]);
var creatorSeed = BuildSeed(
configuration,
sectionName: "Creator",
environmentPrefix: "VTSA_TEAM_CREATOR",
defaultLogin: "sleepy_bao",
defaultDisplayName: "sleepy_bao",
role: AdminRoles.Creator,
fallbackPassword: null);
foreach (var seed in new[] { ownerSeed, creatorSeed })
{
await EnsureMemberAsync(db, seed);
}
await db.SaveChangesAsync();
}
private static async Task EnsureMemberAsync(AwardsDbContext db, TeamAccountSeed seed)
{
if (seed.Credentials is null)
{
return;
}
var member = await db.TeamMembers.FirstOrDefaultAsync(item => item.Login == seed.Login);
if (member is null)
{
db.TeamMembers.Add(new TeamMember
{
Login = seed.Login,
DisplayName = seed.DisplayName,
Role = seed.Role,
PasswordHash = seed.Credentials.Value.Hash,
PasswordSalt = seed.Credentials.Value.Salt,
MustChangePassword = false,
IsActive = true,
CreatedByTwitchId = SeedActor,
CreatedAt = DateTimeOffset.UtcNow,
});
return;
}
var changed = false;
if (!string.Equals(member.DisplayName, seed.DisplayName, StringComparison.Ordinal))
{
member.DisplayName = seed.DisplayName;
changed = true;
}
if (!string.Equals(member.Role, seed.Role, StringComparison.Ordinal))
{
member.Role = seed.Role;
changed = true;
}
if (!member.IsActive)
{
member.IsActive = true;
changed = true;
}
if (changed)
{
member.UpdatedAt = DateTimeOffset.UtcNow;
member.UpdatedByTwitchId = SeedActor;
}
}
private static TeamAccountSeed BuildSeed(
IConfiguration configuration,
string sectionName,
string environmentPrefix,
string defaultLogin,
string defaultDisplayName,
string role,
string? fallbackPassword)
{
var login = ReadSetting(configuration, sectionName, environmentPrefix, "Login") ?? defaultLogin;
var displayName = ReadSetting(configuration, sectionName, environmentPrefix, "DisplayName") ?? defaultDisplayName;
return new TeamAccountSeed(
NormalizeLogin(login),
displayName.Trim(),
role,
ResolveCredentials(configuration, sectionName, environmentPrefix, fallbackPassword));
}
private static TeamSeedCredentials? ResolveCredentials(
IConfiguration configuration,
string sectionName,
string environmentPrefix,
string? fallbackPassword)
{
var configuredHash = ReadSetting(configuration, sectionName, environmentPrefix, "PasswordHash");
var configuredSalt = ReadSetting(configuration, sectionName, environmentPrefix, "PasswordSalt");
if (!string.IsNullOrWhiteSpace(configuredHash) && !string.IsNullOrWhiteSpace(configuredSalt))
{
return new TeamSeedCredentials(configuredHash.Trim(), configuredSalt.Trim());
}
var password = ReadSetting(configuration, sectionName, environmentPrefix, "Password") ?? fallbackPassword;
if (string.IsNullOrWhiteSpace(password))
{
return null;
}
var credentials = DemoCredentialHasher.HashPassword(password);
return new TeamSeedCredentials(credentials.Hash, credentials.Salt);
}
private static string? ReadSetting(IConfiguration configuration, string sectionName, string environmentPrefix, string key)
{
var raw = configuration[$"{environmentPrefix}_{ToEnvironmentKey(key)}"]
?? configuration[$"TeamSeed:{sectionName}:{key}"];
return string.IsNullOrWhiteSpace(raw) ? null : raw;
}
private static string ToEnvironmentKey(string key)
{
return string.Concat(key.Select((character, index) =>
index > 0 && char.IsUpper(character) ? $"_{character}" : character.ToString())).ToUpperInvariant();
}
private static string NormalizeLogin(string value) =>
value.Trim().TrimStart('@').ToLowerInvariant();
private sealed record TeamAccountSeed(string Login, string DisplayName, string Role, TeamSeedCredentials? Credentials);
private readonly record struct TeamSeedCredentials(string Hash, string Salt);
}
@@ -49,6 +49,7 @@ public static class WebApplicationExtensions
await SessionBootstrapper.EnsureAsync(db);
await OperationalTablesBootstrapper.EnsureAsync(db);
await TeamAccountBootstrapper.EnsureAsync(db, app.Configuration);
if (ShouldSeedPresentationData(app))
{
await SeedDataBootstrapper.EnsureAsync(db);