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:
@@ -4,7 +4,7 @@
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public bool IsAdmin { get; set; }
|
||||
public List<string> Roles { get; set; } = new();
|
||||
public bool IsActive { get; set; }
|
||||
public bool MustChangePassword { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using API.Security;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers.Auth
|
||||
@@ -9,10 +9,10 @@ namespace API.Controllers.Auth
|
||||
public class AppUserController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetAppUsers()
|
||||
[Authorize(Policy = PolicyNames.AdminOnly)]
|
||||
public IActionResult GetAppUsers()
|
||||
{
|
||||
return Ok();
|
||||
return Ok(new { message = "Adminzugriff bestätigt." });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +58,13 @@ namespace API.Controllers.Auth
|
||||
if (user is null)
|
||||
return Unauthorized();
|
||||
|
||||
var roles = await userManager.GetRolesAsync(user);
|
||||
|
||||
return Ok(new CurrentUserResponse
|
||||
{
|
||||
Id = user.Id,
|
||||
UserName = user.UserName ?? string.Empty,
|
||||
IsAdmin = user.IsAdmin,
|
||||
Roles = roles.OrderBy(x => x).ToList(),
|
||||
IsActive = user.IsActive,
|
||||
MustChangePassword = user.MustChangePassword
|
||||
});
|
||||
@@ -114,4 +116,4 @@ namespace API.Controllers.Auth
|
||||
return Ok(new { message = "Passwort geändert. Du wurdest auf allen Geräten abgemeldet." });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace API.Database.Configurations
|
||||
|
||||
builder.Property(x => x.CreatedAt).IsRequired();
|
||||
builder.Property(x => x.UpdatedAt).IsRequired();
|
||||
builder.Property(x => x.IsAdmin).IsRequired();
|
||||
builder.Property(x => x.IsActive).IsRequired();
|
||||
builder.Property(x => x.MustChangePassword).IsRequired();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using API.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260420174609_ReplaceIsAdminWithRoles")]
|
||||
partial class ReplaceIsAdminWithRoles
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.6")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("API.Models.AppUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<bool>("MustChangePassword")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("Roles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("RoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("UserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("API.Models.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("API.Models.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Models.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("API.Models.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReplaceIsAdminWithRoles : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
DO $$
|
||||
DECLARE
|
||||
admin_role_id uuid;
|
||||
BEGIN
|
||||
SELECT "Id" INTO admin_role_id
|
||||
FROM "Roles"
|
||||
WHERE "NormalizedName" = 'ADMIN'
|
||||
LIMIT 1;
|
||||
|
||||
IF admin_role_id IS NULL THEN
|
||||
admin_role_id := '2b34c0e2-9d53-4d79-bb85-bff03ce9e1ee';
|
||||
|
||||
INSERT INTO "Roles" ("Id", "Name", "NormalizedName", "ConcurrencyStamp")
|
||||
VALUES (admin_role_id, 'admin', 'ADMIN', NULL)
|
||||
ON CONFLICT ("Id") DO NOTHING;
|
||||
|
||||
SELECT "Id" INTO admin_role_id
|
||||
FROM "Roles"
|
||||
WHERE "NormalizedName" = 'ADMIN'
|
||||
LIMIT 1;
|
||||
END IF;
|
||||
|
||||
INSERT INTO "UserRoles" ("UserId", "RoleId")
|
||||
SELECT u."Id", admin_role_id
|
||||
FROM "Users" u
|
||||
WHERE u."IsAdmin" = TRUE
|
||||
AND admin_role_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "UserRoles" ur
|
||||
WHERE ur."UserId" = u."Id"
|
||||
AND ur."RoleId" = admin_role_id
|
||||
);
|
||||
END $$;
|
||||
""");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsAdmin",
|
||||
table: "Users");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsAdmin",
|
||||
table: "Users",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
UPDATE "Users" u
|
||||
SET "IsAdmin" = TRUE
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM "UserRoles" ur
|
||||
INNER JOIN "Roles" r ON r."Id" = ur."RoleId"
|
||||
WHERE ur."UserId" = u."Id"
|
||||
AND r."NormalizedName" = 'ADMIN'
|
||||
);
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,9 +48,6 @@ namespace API.Migrations
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsAdmin")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace API.Models
|
||||
{
|
||||
public class AppUser : IdentityUser<Guid>
|
||||
{
|
||||
public bool IsAdmin { get; set; } = false;
|
||||
public bool IsActive { get; set; } = true;
|
||||
public bool MustChangePassword { get; set; } = false;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using API.Database;
|
||||
using API.Models;
|
||||
using API.Security;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.HttpLogging;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -50,6 +52,13 @@ builder.Services
|
||||
})
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(PolicyNames.AdminOnly, policy =>
|
||||
{
|
||||
policy.RequireRole(RoleNames.Admin);
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
@@ -59,6 +68,31 @@ builder.Services.ConfigureApplicationCookie(options =>
|
||||
options.AccessDeniedPath = "/auth/forbidden";
|
||||
options.SlidingExpiration = true;
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
OnRedirectToLogin = context =>
|
||||
{
|
||||
if (IsApiRequest(context.Request))
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
context.Response.Redirect(context.RedirectUri);
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnRedirectToAccessDenied = context =>
|
||||
{
|
||||
if (IsApiRequest(context.Request))
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
context.Response.Redirect(context.RedirectUri);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<IdentitySeedService>();
|
||||
@@ -112,3 +146,9 @@ app.MapFallback(async context =>
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
static bool IsApiRequest(HttpRequest request)
|
||||
{
|
||||
return request.Path.StartsWithSegments("/api")
|
||||
|| request.Path.StartsWithSegments("/auth");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace API.Security
|
||||
{
|
||||
public static class PolicyNames
|
||||
{
|
||||
public const string AdminOnly = "admin-only";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace API.Security
|
||||
{
|
||||
public static class RoleNames
|
||||
{
|
||||
public const string Admin = "admin";
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user