using System.Data; using System.Text.Json; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Tiku.Application.Security; using Tiku.Domain.Identity; using Tiku.Domain.Operations; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Bootstrap; public sealed record PlatformAdminBootstrapOptions( string Email, string TemporaryPassword, string? DisplayName = null); public sealed record PlatformAdminBootstrapResult(Guid UserId, Guid RoleId, string Email); public sealed class PlatformAdminBootstrapper( TikuDbContext dbContext, UserManager userManager) { public const string SuperAdminRoleCode = "platform_super_admin"; public async Task BootstrapAsync( PlatformAdminBootstrapOptions options, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(options); var email = options.Email.Trim(); if (email.Length == 0) { throw new ArgumentException("Platform administrator email is required.", nameof(options)); } if (string.IsNullOrWhiteSpace(options.TemporaryPassword)) { throw new ArgumentException("Platform administrator temporary password is required.", nameof(options)); } IDbContextTransaction? transaction = null; if (dbContext.Database.IsRelational()) { transaction = await dbContext.Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken); } await using (transaction) { var existingAdministrator = await ( from binding in dbContext.PlatformBackendUserRoles.AsNoTracking() join boundRole in dbContext.PlatformBackendRoles.AsNoTracking() on binding.RoleId equals boundRole.Id join boundUser in dbContext.Users.AsNoTracking() on binding.UserId equals boundUser.Id where boundRole.Status == BackendRoleStatus.Active && boundUser.Status == UserStatus.Active select boundUser.Id) .AnyAsync(cancellationToken); if (existingAdministrator) { throw new PlatformAdminBootstrapException( "A platform administrator already exists. Bootstrap is a one-time operation.", "platform_admin_already_exists"); } var normalizedEmail = userManager.NormalizeEmail(email); if (await dbContext.Users.AsNoTracking().AnyAsync( user => user.NormalizedEmail == normalizedEmail || user.NormalizedUserName == normalizedEmail, cancellationToken)) { throw new PlatformAdminBootstrapException( "The bootstrap email is already assigned to a user.", "bootstrap_user_already_exists"); } var user = new User { Email = email, UserName = email, Name = string.IsNullOrWhiteSpace(options.DisplayName) ? "Platform Administrator" : options.DisplayName.Trim(), EmailConfirmed = true, Status = UserStatus.Active, ForcePasswordChange = true, TwoFactorEnabled = false }; var createResult = await userManager.CreateAsync(user, options.TemporaryPassword); if (!createResult.Succeeded) { var errors = string.Join(", ", createResult.Errors.Select(error => $"{error.Code}: {error.Description}")); throw new PlatformAdminBootstrapException( $"Platform administrator could not be created: {errors}", "bootstrap_user_invalid"); } var role = new PlatformBackendRole { Code = SuperAdminRoleCode, Name = "Platform Super Administrator", Description = "Built-in role with all platform permissions. Created by the one-time bootstrap command.", Status = BackendRoleStatus.Active, IsSystem = true }; dbContext.PlatformBackendRoles.Add(role); var platformPermissionCodes = BackendPermissions.Platform.ToArray(); var existingPermissionCodes = await dbContext.BackendPermissions .Where(permission => platformPermissionCodes.Contains(permission.Code)) .Select(permission => permission.Code) .ToHashSetAsync(StringComparer.Ordinal, cancellationToken); foreach (var permissionCode in platformPermissionCodes.Where(code => !existingPermissionCodes.Contains(code))) { dbContext.BackendPermissions.Add(new BackendPermission { Code = permissionCode, Name = permissionCode, Area = BackendPermissionArea.Platform, Module = "platform", Description = "Built-in platform permission.", IsSystem = true }); } dbContext.PlatformBackendRolePermissions.AddRange( platformPermissionCodes.Select(permissionCode => new PlatformBackendRolePermission { RoleId = role.Id, PermissionCode = permissionCode })); dbContext.PlatformBackendUserRoles.Add(new PlatformBackendUserRole { UserId = user.Id, RoleId = role.Id }); dbContext.AuditLogs.Add(new AuditLog { ActorUserId = user.Id, Action = "platform.bootstrap_admin.created", TargetType = "users", TargetId = user.Id.ToString(), Details = JsonSerializer.SerializeToElement(new { user.Email, RoleCode = SuperAdminRoleCode, ForcePasswordChange = true, MfaEnrollmentRequired = true }) }); await dbContext.SaveChangesAsync(cancellationToken); if (transaction is not null) { await transaction.CommitAsync(cancellationToken); } return new PlatformAdminBootstrapResult(user.Id, role.Id, email); } } } public sealed class PlatformAdminBootstrapException(string message, string code) : InvalidOperationException(message) { public string Code { get; } = code; }