feat: separate create/edit form pages, Playwright test infrastructure, /auth/token endpoint

Move all CRUD create/edit forms from inline on list pages to dedicated form pages
with back-button navigation and post-save redirect. Add Playwright Docker container
(browser server on port 3000) with 25 passing E2E tests covering login, navigation,
and site CRUD workflows. Add POST /auth/token endpoint for clean JWT retrieval.
This commit is contained in:
Joseph Doherty
2026-03-21 15:17:24 -04:00
parent b3f8850711
commit d3194e3634
31 changed files with 2333 additions and 1117 deletions

View File

@@ -80,6 +80,47 @@ public static class AuthEndpoints
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<LdapAuthService>();
var jwtService = context.RequestServices.GetRequiredService<JwtTokenService>();
var roleMapper = context.RequestServices.GetRequiredService<RoleMapper>();
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);