Replace IsAdmin with role-based admin

Switch user admin handling from an AppUser boolean to ASP.NET Identity roles. Removed AppUser.IsAdmin and related configuration/model entries; added migration ReplaceIsAdminWithRoles to copy Users.IsAdmin=true into a persistent admin role and drop the IsAdmin column. CurrentUserResponse now exposes roles (string[]), AuthController returns ordered roles from UserManager, and IdentitySeedService now ensures the admin role exists and assigns/creates an initial admin user in that role. Program.cs registers an Admin-only policy (PolicyNames/RoleNames), adjusts cookie auth events to return 401/403 for API requests, and wires up authorization. Frontend updated to use roles: authSession normalizes roles, adds hasRole and ROLE_ADMIN, router and layout support meta.requiredRoles, and new Forbidden and AdminUsers pages/route are added. codexInfo.md updated to reflect the migration to role-based auth.
This commit is contained in:
Jonas
2026-04-20 19:57:49 +02:00
parent bd261b6868
commit b2984fcf1a
19 changed files with 813 additions and 39 deletions
+54 -19
View File
@@ -1,21 +1,23 @@
using API.Models;
using API.Security;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace API.Services
{
public class IdentitySeedService(
UserManager<AppUser> userManager,
RoleManager<IdentityRole<Guid>> roleManager,
IConfiguration configuration,
ILogger<IdentitySeedService> logger)
{
public async Task SeedAsync()
{
var hasAdmin = await userManager.Users.AnyAsync(x => x.IsAdmin);
await EnsureRoleExistsAsync(RoleNames.Admin);
if (hasAdmin)
var adminUsers = await userManager.GetUsersInRoleAsync(RoleNames.Admin);
if (adminUsers.Count > 0)
{
logger.LogDebug("Admin-Seed übersprungen: Es existiert bereits ein Admin-Account.");
logger.LogDebug("Admin-Seed übersprungen: Es existiert bereits ein Admin über Rollen.");
return;
}
@@ -23,30 +25,63 @@ namespace API.Services
var adminPassword = configuration["SeedAdmin:Password"] ?? "HoardPassword";
var adminEmail = configuration["SeedAdmin:Email"];
var admin = new AppUser
var admin = await userManager.FindByNameAsync(adminUserName);
if (admin is null)
{
UserName = adminUserName,
Email = string.IsNullOrWhiteSpace(adminEmail) ? null : adminEmail,
IsAdmin = true,
IsActive = true,
MustChangePassword = true,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
};
admin = new AppUser
{
UserName = adminUserName,
Email = string.IsNullOrWhiteSpace(adminEmail) ? null : adminEmail,
IsActive = true,
MustChangePassword = true,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
};
var result = await userManager.CreateAsync(admin, adminPassword);
var createResult = await userManager.CreateAsync(admin, adminPassword);
if (!createResult.Succeeded)
{
var createErrors = string.Join(", ", createResult.Errors.Select(x => x.Description));
logger.LogError("Admin-Seed fehlgeschlagen: {Errors}", createErrors);
throw new InvalidOperationException($"Admin-Seed fehlgeschlagen: {createErrors}");
}
}
if (!result.Succeeded)
if (!await userManager.IsInRoleAsync(admin, RoleNames.Admin))
{
var errors = string.Join(", ", result.Errors.Select(x => x.Description));
logger.LogError("Admin-Seed fehlgeschlagen: {Errors}", errors);
throw new InvalidOperationException($"Admin-Seed fehlgeschlagen: {errors}");
var roleResult = await userManager.AddToRoleAsync(admin, RoleNames.Admin);
if (!roleResult.Succeeded)
{
var roleErrors = string.Join(", ", roleResult.Errors.Select(x => x.Description));
logger.LogError("Admin-Rollenzuweisung fehlgeschlagen: {Errors}", roleErrors);
throw new InvalidOperationException($"Admin-Rollenzuweisung fehlgeschlagen: {roleErrors}");
}
}
logger.LogInformation(
"Admin-Account wurde geseedet (UserName: {UserName}, Email: {Email}).",
"Admin-Account wurde geseedet bzw. als Rolle zugewiesen (UserName: {UserName}, Email: {Email}).",
admin.UserName,
admin.Email ?? "(keine)");
}
private async Task EnsureRoleExistsAsync(string roleName)
{
if (await roleManager.RoleExistsAsync(roleName))
{
return;
}
var createRoleResult = await roleManager.CreateAsync(new IdentityRole<Guid>
{
Name = roleName
});
if (!createRoleResult.Succeeded)
{
var roleErrors = string.Join(", ", createRoleResult.Errors.Select(x => x.Description));
logger.LogError("Rolle {RoleName} konnte nicht erstellt werden: {Errors}", roleName, roleErrors);
throw new InvalidOperationException($"Rolle {roleName} konnte nicht erstellt werden: {roleErrors}");
}
}
}
}