From 114025fcfb651b450279e162328c9d7fb68242da Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Wed, 14 Jan 2026 22:04:37 -0600 Subject: [PATCH] all the things --- .../BackEnd/Controllers/AccountController.cs | 35 +++++++- ...entityApiEndpointRouteBuilderExtensions.cs | 2 +- .../BackEnd/Controllers/SessionsController.cs | 2 +- .../CookieAuthenticationStateProvider.cs | 37 ++++---- .../FrontEnd/Components/Layout/NavMenu.razor | 4 +- .../Components/Layout/NavMenu.razor.css | 5 +- .../Components/Pages/Home/HomePage.razor | 76 ++++++++++++----- .../Components/Pages/Home/HomePage.razor.css | 69 ++++++++++++++- .../Home/VisualAids/PieceMovesVisualAid.razor | 8 +- .../VisualAids/PromotedPieceVisualAid.razor | 4 +- .../Components/Pages/Identity/LoginPage.razor | 2 +- Shogi/Program.cs | 84 +++++++++---------- Shogi/Shogi.csproj | 4 + 13 files changed, 233 insertions(+), 99 deletions(-) diff --git a/Shogi/BackEnd/Controllers/AccountController.cs b/Shogi/BackEnd/Controllers/AccountController.cs index dda50b7..0c8f402 100644 --- a/Shogi/BackEnd/Controllers/AccountController.cs +++ b/Shogi/BackEnd/Controllers/AccountController.cs @@ -2,17 +2,20 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Shogi.BackEnd.Identity; +using Shogi.BackEnd.Repositories; using System.Security.Claims; namespace Shogi.BackEnd.Controllers; [Authorize] -[Route("[controller]")] +[Route("backend/[controller]")] [ApiController] public class AccountController( SignInManager signInManager, UserManager UserManager, - IConfiguration configuration) : ControllerBase + IConfiguration configuration, + SessionRepository sessionRepository, + QueryRepository queryRepository) : ControllerBase { [Authorize("Admin")] [HttpPost("TestAccount")] @@ -36,7 +39,31 @@ public class AccountController( return this.Created(); } - [HttpPost("/logout")] + [Authorize("Admin")] + [HttpDelete("TestAccount")] + public async Task DeleteTestAccounts() + { + var testUsers = new[] { "aat-account", "aat-account-2" }; + + foreach (var username in testUsers) + { + var user = await UserManager.FindByNameAsync(username); + if (user != null) + { + var sessions = await queryRepository.ReadSessionsMetadata(user.Id); + foreach (var session in sessions) + { + await sessionRepository.DeleteSession(session.Id); + } + + await UserManager.DeleteAsync(user); + } + } + + return this.Ok(); + } + + [HttpPost("/backend/logout")] public async Task Logout([FromBody] object empty) { // https://learn.microsoft.com/aspnet/core/blazor/security/webassembly/standalone-with-identity#antiforgery-support @@ -50,7 +77,7 @@ public class AccountController( return this.Unauthorized(); } - [HttpGet("/roles")] + [HttpGet("/backend/roles")] public IActionResult GetRoles() { if (this.User.Identity is not null && this.User.Identity.IsAuthenticated) diff --git a/Shogi/BackEnd/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs b/Shogi/BackEnd/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs index d64764f..493521a 100644 --- a/Shogi/BackEnd/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs +++ b/Shogi/BackEnd/Controllers/MyIdentityApiEndpointRouteBuilderExtensions.cs @@ -47,7 +47,7 @@ public static class MyIdentityApiEndpointRouteBuilderExtensions // We'll figure out a unique endpoint name based on the final route pattern during endpoint generation. string? confirmEmailEndpointName = null; - var routeGroup = endpoints.MapGroup(""); + var routeGroup = endpoints.MapGroup("backend"); // NOTE: We cannot inject UserManager directly because the TUser generic parameter is currently unsupported by RDG. // https://github.com/dotnet/aspnetcore/issues/47338 diff --git a/Shogi/BackEnd/Controllers/SessionsController.cs b/Shogi/BackEnd/Controllers/SessionsController.cs index f6695f6..91076cb 100644 --- a/Shogi/BackEnd/Controllers/SessionsController.cs +++ b/Shogi/BackEnd/Controllers/SessionsController.cs @@ -9,7 +9,7 @@ namespace Shogi.BackEnd.Controllers; [Authorize] [ApiController] -[Route("[controller]")] +[Route("backend/[controller]")] public class SessionsController( SessionRepository sessionRepository, ShogiApplication application) : ControllerBase diff --git a/Shogi/FrontEnd/Client/CookieAuthenticationStateProvider.cs b/Shogi/FrontEnd/Client/CookieAuthenticationStateProvider.cs index fb5f1ac..30fc561 100644 --- a/Shogi/FrontEnd/Client/CookieAuthenticationStateProvider.cs +++ b/Shogi/FrontEnd/Client/CookieAuthenticationStateProvider.cs @@ -10,7 +10,11 @@ using System.Text.Json; /// /// Handles state for cookie-based auth. /// -public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IAccountManagement +/// +/// Create a new instance of the auth provider. +/// +/// Factory to retrieve auth client. +public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory) : AuthenticationStateProvider, IAccountManagement { /// /// Map the JavaScript-formatted properties to C#-formatted classes. @@ -23,12 +27,12 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA /// /// Special auth client. /// - private readonly HttpClient _httpClient; + private readonly HttpClient httpClient = httpClientFactory.CreateClient("Auth"); /// /// Authentication state. /// - private bool _authenticated = false; + private bool authenticated = false; /// /// Default principal for anonymous (not authenticated) users. @@ -36,13 +40,6 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA private readonly ClaimsPrincipal Unauthenticated = new(new ClaimsIdentity()); - /// - /// Create a new instance of the auth provider. - /// - /// Factory to retrieve auth client. - public CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory) - => _httpClient = httpClientFactory.CreateClient("Auth"); - /// /// Register a new user. /// @@ -57,7 +54,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA try { // make the request - var result = await _httpClient.PostAsJsonAsync("register", new + var result = await httpClient.PostAsJsonAsync("backend/register", new { email, password @@ -118,7 +115,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA try { // login with cookies - var result = await _httpClient.PostAsJsonAsync("login?useCookies=true", new + var result = await httpClient.PostAsJsonAsync("backend/login?useCookies=true", new { email, password @@ -154,7 +151,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA /// The authentication state asynchronous request. public override async Task GetAuthenticationStateAsync() { - _authenticated = false; + authenticated = false; // default to not authenticated var user = Unauthenticated; @@ -162,7 +159,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA try { // the user info endpoint is secured, so if the user isn't logged in this will fail - var userResponse = await _httpClient.GetAsync("manage/info"); + var userResponse = await httpClient.GetAsync("backend/manage/info"); // throw if user info wasn't retrieved userResponse.EnsureSuccessStatusCode(); @@ -187,7 +184,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA .Select(c => new Claim(c.Key, c.Value))); // tap the roles endpoint for the user's roles - var rolesResponse = await _httpClient.GetAsync("roles"); + var rolesResponse = await httpClient.GetAsync("backend/roles"); // throw if request fails rolesResponse.EnsureSuccessStatusCode(); @@ -213,7 +210,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA // set the principal var id = new ClaimsIdentity(claims, nameof(CookieAuthenticationStateProvider)); user = new ClaimsPrincipal(id); - _authenticated = true; + authenticated = true; } } catch { } @@ -226,14 +223,14 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA { const string Empty = "{}"; var emptyContent = new StringContent(Empty, Encoding.UTF8, "application/json"); - await _httpClient.PostAsync("logout", emptyContent); + await httpClient.PostAsync("backend/logout", emptyContent); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } public async Task CheckAuthenticatedAsync() { await GetAuthenticationStateAsync(); - return _authenticated; + return authenticated; } /// @@ -242,7 +239,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA /// Do not surface errors from this to users which may tell bad actors if emails do or do not exist in the system. public async Task RequestPasswordReset(string email) { - return await _httpClient.PostAsJsonAsync("forgotPassword", new { email }); + return await httpClient.PostAsJsonAsync("backend/forgotPassword", new { email }); } public async Task ChangePassword(string email, string resetCode, string newPassword) @@ -253,7 +250,7 @@ public class CookieAuthenticationStateProvider : AuthenticationStateProvider, IA resetCode, newPassword }; - var response = await _httpClient.PostAsJsonAsync("resetPassword", body); + var response = await httpClient.PostAsJsonAsync("backend/resetPassword", body); if (response.IsSuccessStatusCode) { return new FormResult { Succeeded = true }; diff --git a/Shogi/FrontEnd/Components/Layout/NavMenu.razor b/Shogi/FrontEnd/Components/Layout/NavMenu.razor index 2086597..9a7047b 100644 --- a/Shogi/FrontEnd/Components/Layout/NavMenu.razor +++ b/Shogi/FrontEnd/Components/Layout/NavMenu.razor @@ -3,7 +3,6 @@ @* Desktop view *@