using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.EntityFrameworkCore; using MudBlazor.Services; using timetracker.Components; using timetracker.Data; var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/login"; options.LogoutPath = "/auth/logout"; options.ExpireTimeSpan = TimeSpan.FromDays(30); options.SlidingExpiration = true; }); builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireClaim(System.Security.Claims.ClaimTypes.Name, "marc")); }); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); builder.Services.AddMudServices(); builder.Services.AddHttpClient(); var dbPath = Environment.GetEnvironmentVariable("TIMETRACKER_DB_PATH") ?? Path.Combine(builder.Environment.ContentRootPath, "timetracker.db"); builder.Services.AddDbContextFactory(options => options.UseSqlite($"Data Source={dbPath}")); builder.Services.AddScoped(); var app = builder.Build(); using (var scope = app.Services.CreateScope()) { var factory = scope.ServiceProvider.GetRequiredService>(); await using var db = await factory.CreateDbContextAsync(); await db.Database.MigrateAsync(); } // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); if (app.Configuration.GetValue("EnableHttpsRedirect", !app.Environment.IsDevelopment())) { app.UseHttpsRedirection(); } app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); // ── Auth-Endpoints ──────────────────────────────────────────────────────────── app.MapPost("/auth/login", async (HttpContext ctx, AuthService authService) => { var form = await ctx.Request.ReadFormAsync(); var username = form["username"].ToString(); var password = form["password"].ToString(); var user = await authService.LoginAsync(username, password); if (user == null) return Results.Redirect("/login?error=invalid"); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username) }; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), new AuthenticationProperties { IsPersistent = true }); return Results.Redirect("/"); }).DisableAntiforgery(); app.MapPost("/auth/register", async (HttpContext ctx, AuthService authService) => { var form = await ctx.Request.ReadFormAsync(); var username = form["username"].ToString(); var password = form["password"].ToString(); var (user, error) = await authService.RegisterAsync(username, password); if (user == null) return Results.Redirect($"/login?tab=register&error={Uri.EscapeDataString(error!)}"); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username) }; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), new AuthenticationProperties { IsPersistent = true }); return Results.Redirect("/"); }).DisableAntiforgery(); app.MapGet("/auth/logout", async (HttpContext ctx) => { await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Results.Redirect("/login"); }); app.Run();