Seed personal team accounts
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user