using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using ScadaLink.Security; namespace ScadaLink.CentralUI.Auth; /// /// Minimal API endpoints for login/logout. These run outside Blazor Server (standard HTTP POST). /// On success, signs in via ASP.NET Core cookie authentication and redirects to dashboard. /// public static class AuthEndpoints { public static IEndpointRouteBuilder MapAuthEndpoints(this IEndpointRouteBuilder endpoints) { endpoints.MapPost("/auth/login", async (HttpContext context) => { var form = await context.Request.ReadFormAsync(); var username = form["username"].ToString(); var password = form["password"].ToString(); if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) { context.Response.Redirect("/login?error=Username+and+password+are+required."); return; } var ldapAuth = context.RequestServices.GetRequiredService(); var jwtService = context.RequestServices.GetRequiredService(); var roleMapper = context.RequestServices.GetRequiredService(); var authResult = await ldapAuth.AuthenticateAsync(username, password); if (!authResult.Success) { var errorMsg = Uri.EscapeDataString(authResult.ErrorMessage ?? "Authentication failed."); context.Response.Redirect($"/login?error={errorMsg}"); return; } // Map LDAP groups to roles var roleMappingResult = await roleMapper.MapGroupsToRolesAsync(authResult.Groups ?? []); // Build claims from LDAP auth + role mapping var claims = new List { new(ClaimTypes.Name, authResult.Username ?? username), new(JwtTokenService.DisplayNameClaimType, authResult.DisplayName ?? username), new(JwtTokenService.UsernameClaimType, authResult.Username ?? username), }; foreach (var role in roleMappingResult.Roles) { claims.Add(new Claim(JwtTokenService.RoleClaimType, role)); } if (!roleMappingResult.IsSystemWideDeployment) { foreach (var siteId in roleMappingResult.PermittedSiteIds) { claims.Add(new Claim(JwtTokenService.SiteIdClaimType, siteId)); } } var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); await context.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30) }); context.Response.Redirect("/"); }).DisableAntiforgery(); endpoints.MapPost("/auth/token", async (HttpContext context) => { var form = await context.Request.ReadFormAsync(); var username = form["username"].ToString(); var password = form["password"].ToString(); if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) { return Results.Json(new { error = "Username and password are required." }, statusCode: 400); } var ldapAuth = context.RequestServices.GetRequiredService(); var jwtService = context.RequestServices.GetRequiredService(); var roleMapper = context.RequestServices.GetRequiredService(); var authResult = await ldapAuth.AuthenticateAsync(username, password); if (!authResult.Success) { return Results.Json( new { error = authResult.ErrorMessage ?? "Authentication failed." }, statusCode: 401); } var roleMappingResult = await roleMapper.MapGroupsToRolesAsync(authResult.Groups ?? []); var token = jwtService.GenerateToken( authResult.DisplayName ?? username, authResult.Username ?? username, roleMappingResult.Roles, roleMappingResult.IsSystemWideDeployment ? null : roleMappingResult.PermittedSiteIds); return Results.Json(new { access_token = token, token_type = "Bearer", username = authResult.Username ?? username, display_name = authResult.DisplayName ?? username, roles = roleMappingResult.Roles, }); }).DisableAntiforgery(); endpoints.MapPost("/auth/logout", async (HttpContext context) => { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); context.Response.Redirect("/login"); }).DisableAntiforgery(); // GET /logout — allows direct navigation to logout (redirects to login after sign-out) endpoints.MapGet("/logout", async (HttpContext context) => { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Results.Redirect("/login"); }); return endpoints; } }