Files
Hoard/API/Program.cs
T
Jonas b2984fcf1a 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.
2026-04-20 19:57:49 +02:00

155 lines
4.6 KiB
C#

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;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("appsettings.custom.json", optional: true, reloadOnChange: true);
builder.Logging.ClearProviders();
builder.Logging.AddSimpleConsole(options =>
{
options.SingleLine = true;
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
});
builder.Logging.AddDebug();
var connectionString = builder.Configuration.GetConnectionString("Postgres")
?? throw new InvalidOperationException("Connection string 'Postgres' wurde nicht gefunden.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseNpgsql(connectionString);
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpLogging(options =>
{
options.LoggingFields = HttpLoggingFields.RequestMethod
| HttpLoggingFields.RequestPath
| HttpLoggingFields.ResponseStatusCode
| HttpLoggingFields.Duration;
});
builder.Services
.AddIdentity<AppUser, IdentityRole<Guid>>(options =>
{
options.Password.RequiredLength = 8;
options.Password.RequireDigit = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.User.RequireUniqueEmail = false;
options.SignIn.RequireConfirmedAccount = false;
options.SignIn.RequireConfirmedEmail = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(PolicyNames.AdminOnly, policy =>
{
policy.RequireRole(RoleNames.Admin);
});
});
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "hoard.auth";
options.LoginPath = "/auth/login";
options.LogoutPath = "/auth/logout";
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>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var startupLogger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
startupLogger.LogInformation("Starte Datenbankmigrationen.");
dbContext.Database.Migrate();
var seedService = scope.ServiceProvider.GetRequiredService<IdentitySeedService>();
await seedService.SeedAsync();
startupLogger.LogInformation("Backend-Initialisierung abgeschlossen.");
}
var webRootPath = app.Environment.WebRootPath ?? Path.Combine(app.Environment.ContentRootPath, "wwwroot");
var indexFilePath = Path.Combine(webRootPath, "index.html");
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpLogging();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapFallback(async context =>
{
if (context.Request.Path.StartsWithSegments("/api"))
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
return;
}
if (!File.Exists(indexFilePath))
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
return;
}
context.Response.ContentType = "text/html; charset=utf-8";
await context.Response.SendFileAsync(indexFilePath);
});
app.Run();
static bool IsApiRequest(HttpRequest request)
{
return request.Path.StartsWithSegments("/api")
|| request.Path.StartsWithSegments("/auth");
}