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);
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ public static class WebApplicationExtensions
|
|||||||
|
|
||||||
await SessionBootstrapper.EnsureAsync(db);
|
await SessionBootstrapper.EnsureAsync(db);
|
||||||
await OperationalTablesBootstrapper.EnsureAsync(db);
|
await OperationalTablesBootstrapper.EnsureAsync(db);
|
||||||
|
await TeamAccountBootstrapper.EnsureAsync(db, app.Configuration);
|
||||||
if (ShouldSeedPresentationData(app))
|
if (ShouldSeedPresentationData(app))
|
||||||
{
|
{
|
||||||
await SeedDataBootstrapper.EnsureAsync(db);
|
await SeedDataBootstrapper.EnsureAsync(db);
|
||||||
|
|||||||
Reference in New Issue
Block a user