From 7187631ebc60e5975286b8b39cf159fbef6c134b Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Fri, 11 Sep 2026 23:14:30 -0400 Subject: [PATCH] Move Infrastructure.Jwt into Features.Users (#287) --- docs/web/architecture.md | 10 +++------- docs/web/diagrams/vertical-slice.md | 2 +- .../vertical-slice/project-references.mmd | 3 +-- docs/web/token-validation.md | 15 +++++++-------- web/backend/API/API.Core/API.Core.csproj | 1 - .../Authentication/JwtAuthenticationHandler.cs | 2 +- web/backend/API/API.Core/Dockerfile | 1 - .../API/API.Core/ServiceCollectionExtensions.cs | 3 --- web/backend/API/API.Specs/Dockerfile | 1 - web/backend/API/API.Specs/Steps/AuthSteps.cs | 2 +- web/backend/Biergarten.API.slnx | 1 - .../Services/TokenServiceRefreshTests.cs | 1 - .../Services/TokenServiceValidationTests.cs | 1 - .../FeaturesUsersServiceCollectionExtensions.cs | 1 + .../Features.Users/Features.Users.csproj | 3 ++- .../Services}/ITokenInfrastructure.cs | 2 +- .../Services}/JwtInfrastructure.cs | 2 +- .../Features.Users/Services/TokenService.cs | 1 - .../Infrastructure.Jwt.csproj | 17 ----------------- 19 files changed, 19 insertions(+), 50 deletions(-) rename web/backend/{Infrastructure/Infrastructure.Jwt => Features/Features.Users/Services}/ITokenInfrastructure.cs (94%) rename web/backend/{Infrastructure/Infrastructure.Jwt => Features/Features.Users/Services}/JwtInfrastructure.cs (98%) delete mode 100644 web/backend/Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj diff --git a/docs/web/architecture.md b/docs/web/architecture.md index e11f7c1b..85b20264 100644 --- a/docs/web/architecture.md +++ b/docs/web/architecture.md @@ -89,7 +89,6 @@ flowchart TB subgraph INFRA["Infrastructure Layer"] direction LR I1["Database.Connection"] - I2["Infrastructure.Jwt"] I3["Infrastructure.PasswordHashing"] I4["Infrastructure.Email /
Email.Templates"] I5["Infrastructure.FileUpload"] @@ -133,7 +132,6 @@ own - Every `Features.*` project (for controller/MediatR discovery) - `Shared.Contracts`, `Shared.Application` -- `Infrastructure.Jwt` (for the auth middleware) **Rules**: @@ -167,10 +165,9 @@ brewery photo upload command) rather than bound to an HTTP route directly. - `Domain.Entities`, `Domain.Exceptions` - `Database.Connection` (generic ADO.NET connection/command plumbing) plus - whichever infrastructure project the slice needs (`Infrastructure.Jwt`/ - `Infrastructure.PasswordHashing` for Users, `Infrastructure.Email`/ - `Infrastructure.Email.Templates` for Emails, `Infrastructure.FileUpload` for - PhotoUpload) + whichever infrastructure project the slice needs (`Infrastructure.PasswordHashing` + for Users, `Infrastructure.Email`/`Infrastructure.Email.Templates` for + Emails, `Infrastructure.FileUpload` for PhotoUpload) - `Shared.Contracts`, `Shared.Application` - No other `Features.*` project, with two exceptions: `Features.Users` references `Features.PhotoUpload` directly for avatar uploads, and @@ -210,7 +207,6 @@ whichever slices need them | Project | Description | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Database.Connection` | Generic ADO.NET connection/command plumbing (`DefaultSqlConnectionFactory`, the abstract `DapperRepository` base class), not domain-specific; each slice's own `Repository/` folder builds on this. | -| `Infrastructure.Jwt` | JWT token generation and validation. | | `Infrastructure.PasswordHashing` | Argon2id password hashing. | | `Infrastructure.Email` | Email sending capabilities (SMTP/MailKit). | | `Infrastructure.Email.Templates` | Email template rendering (Razor components). | diff --git a/docs/web/diagrams/vertical-slice.md b/docs/web/diagrams/vertical-slice.md index 44c7a1c1..02285b69 100644 --- a/docs/web/diagrams/vertical-slice.md +++ b/docs/web/diagrams/vertical-slice.md @@ -97,7 +97,7 @@ see the notes for what else each of those foundation projects pulls in. - **Omitted from the diagram**: - `Domain.Exceptions` (referenced by nearly every project directly, including - all of `Shared.Contracts`, `Infrastructure.Jwt`, and each feature slice) + all of `Shared.Contracts` and each feature slice) - `Infrastructure.Configuration` (the shared options-binding project referenced by `Database.Connection`, `Infrastructure.Email`, `Infrastructure.FileUpload`, and `Features.Emails` directly) diff --git a/docs/web/diagrams/vertical-slice/project-references.mmd b/docs/web/diagrams/vertical-slice/project-references.mmd index 5ea0a881..982666e3 100644 --- a/docs/web/diagrams/vertical-slice/project-references.mmd +++ b/docs/web/diagrams/vertical-slice/project-references.mmd @@ -23,7 +23,6 @@ end subgraph INFRA["Slice-specific Infrastructure"] direction LR -JWT["Infrastructure.Jwt"] PwdHash["Infrastructure.PasswordHashing"] FileUpload["Infrastructure.FileUpload"] Email["Infrastructure.Email"] @@ -36,6 +35,6 @@ BrewerySlice --> FOUNDATION LocationsSlice --> FOUNDATION BeersSlice --> FOUNDATION PhotoSlice --> FOUNDATION & FileUpload -UsersSlice --> FOUNDATION & JWT & PwdHash +UsersSlice --> FOUNDATION & PwdHash EmailsSlice --> Email & EmailTemplates DBConn -- Dapper SQL, no ORM --> Tables[("SQL Server
Tables")] diff --git a/docs/web/token-validation.md b/docs/web/token-validation.md index aff82837..d37ae96d 100644 --- a/docs/web/token-validation.md +++ b/docs/web/token-validation.md @@ -22,7 +22,7 @@ The Biergarten API validates JSON Web Tokens (JWTs) across three token types: ### Infrastructure layer -#### [ITokenInfrastructure](../../web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs) +#### [ITokenInfrastructure](../../web/backend/Features/Features.Users/Services/ITokenInfrastructure.cs) Low-level JWT operations. @@ -32,7 +32,7 @@ Low-level JWT operations. - `ValidateJwtAsync()` - Validates token signature, expiration, and format **Implementation:** -[JwtInfrastructure.cs](../../web/backend/Infrastructure/Infrastructure.Jwt/JwtInfrastructure.cs) +[JwtInfrastructure.cs](../../web/backend/Features/Features.Users/Services/JwtInfrastructure.cs) - Uses Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler - Algorithm: HS256 (HMAC-SHA256) @@ -76,12 +76,11 @@ directly, bypassing the `Features.Users` slice entirely. - Extracts and validates claims (Sub, UniqueName) - Throws `UnauthorizedException` on validation failure -`TokenService` is registered by `Features.Users`' own `AddFeaturesUsers()` -extension method, except for the lower-level `ITokenInfrastructure` (JWT -signing/verification) it depends on, which is registered by the host -(`API.Core/ServiceCollectionExtensions.cs`, via `AddCoreInfrastructure()`, -called from `Program.cs`) since `JwtAuthenticationHandler` (host-level auth -middleware) also depends on it directly. +`TokenService` and the lower-level `ITokenInfrastructure` (JWT +signing/verification) it depends on are both registered by `Features.Users`' +own `AddFeaturesUsers()` extension method. `JwtAuthenticationHandler` +(host-level auth middleware) also depends on `ITokenInfrastructure` directly, +resolving it from the same DI container. ### Integration points diff --git a/web/backend/API/API.Core/API.Core.csproj b/web/backend/API/API.Core/API.Core.csproj index 2cc523a4..4e4abe30 100644 --- a/web/backend/API/API.Core/API.Core.csproj +++ b/web/backend/API/API.Core/API.Core.csproj @@ -23,7 +23,6 @@ - diff --git a/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs b/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs index d838cd24..b51e808f 100644 --- a/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs +++ b/web/backend/API/API.Core/Authentication/JwtAuthenticationHandler.cs @@ -1,7 +1,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; +using Features.Users.Services; using Infrastructure.Configuration; -using Infrastructure.Jwt; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; diff --git a/web/backend/API/API.Core/Dockerfile b/web/backend/API/API.Core/Dockerfile index 867f0a9f..c8b111bd 100644 --- a/web/backend/API/API.Core/Dockerfile +++ b/web/backend/API/API.Core/Dockerfile @@ -36,7 +36,6 @@ COPY ["Features/Features.Users.Tests/Features.Users.Tests.csproj", "Features/Fea COPY ["Infrastructure/Infrastructure.Configuration/Infrastructure.Configuration.csproj", "Infrastructure/Infrastructure.Configuration/"] COPY ["Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj", "Infrastructure/Infrastructure.Email/"] COPY ["Infrastructure/Infrastructure.FileUpload/Infrastructure.FileUpload.csproj", "Infrastructure/Infrastructure.FileUpload/"] -COPY ["Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj", "Infrastructure/Infrastructure.Jwt/"] COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] RUN dotnet restore "Biergarten.API.slnx" diff --git a/web/backend/API/API.Core/ServiceCollectionExtensions.cs b/web/backend/API/API.Core/ServiceCollectionExtensions.cs index d3621cf0..d8d01a8c 100644 --- a/web/backend/API/API.Core/ServiceCollectionExtensions.cs +++ b/web/backend/API/API.Core/ServiceCollectionExtensions.cs @@ -13,7 +13,6 @@ using Features.Users.DependencyInjection; using FluentValidation; using FluentValidation.AspNetCore; using Infrastructure.FileUpload; -using Infrastructure.Jwt; using Microsoft.OpenApi.Models; using Shared.Application.Behaviors; @@ -105,8 +104,6 @@ internal static class ServiceCollectionExtensions public IServiceCollection AddCoreInfrastructure() => services - // ITokenInfrastructure is registered here because JwtAuthenticationHandler depends on it directly. - .AddScoped() .AddSingleton() .AddScoped(); diff --git a/web/backend/API/API.Specs/Dockerfile b/web/backend/API/API.Specs/Dockerfile index 008695b6..c0b248a2 100644 --- a/web/backend/API/API.Specs/Dockerfile +++ b/web/backend/API/API.Specs/Dockerfile @@ -26,7 +26,6 @@ COPY ["Features/Features.Users.Tests/Features.Users.Tests.csproj", "Features/Fea COPY ["Infrastructure/Infrastructure.Configuration/Infrastructure.Configuration.csproj", "Infrastructure/Infrastructure.Configuration/"] COPY ["Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj", "Infrastructure/Infrastructure.Email/"] COPY ["Infrastructure/Infrastructure.FileUpload/Infrastructure.FileUpload.csproj", "Infrastructure/Infrastructure.FileUpload/"] -COPY ["Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj", "Infrastructure/Infrastructure.Jwt/"] COPY ["Shared/Shared.Application/Shared.Application.csproj", "Shared/Shared.Application/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] RUN dotnet restore "Biergarten.API.slnx" diff --git a/web/backend/API/API.Specs/Steps/AuthSteps.cs b/web/backend/API/API.Specs/Steps/AuthSteps.cs index ab889921..90d6e7c4 100644 --- a/web/backend/API/API.Specs/Steps/AuthSteps.cs +++ b/web/backend/API/API.Specs/Steps/AuthSteps.cs @@ -1,8 +1,8 @@ using System.Text; using System.Text.Json; +using Features.Users.Services; using FluentAssertions; using Infrastructure.Configuration; -using Infrastructure.Jwt; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Reqnroll; diff --git a/web/backend/Biergarten.API.slnx b/web/backend/Biergarten.API.slnx index d24083dd..c49f3bee 100644 --- a/web/backend/Biergarten.API.slnx +++ b/web/backend/Biergarten.API.slnx @@ -23,7 +23,6 @@ - diff --git a/web/backend/Features/Features.Users.Tests/Services/TokenServiceRefreshTests.cs b/web/backend/Features/Features.Users.Tests/Services/TokenServiceRefreshTests.cs index 581cac4c..04c9b242 100644 --- a/web/backend/Features/Features.Users.Tests/Services/TokenServiceRefreshTests.cs +++ b/web/backend/Features/Features.Users.Tests/Services/TokenServiceRefreshTests.cs @@ -6,7 +6,6 @@ using Features.Users.Services; using Features.Users.Tests.TestSupport; using FluentAssertions; using Infrastructure.Configuration; -using Infrastructure.Jwt; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Moq; diff --git a/web/backend/Features/Features.Users.Tests/Services/TokenServiceValidationTests.cs b/web/backend/Features/Features.Users.Tests/Services/TokenServiceValidationTests.cs index b29fdf4c..e4782b1a 100644 --- a/web/backend/Features/Features.Users.Tests/Services/TokenServiceValidationTests.cs +++ b/web/backend/Features/Features.Users.Tests/Services/TokenServiceValidationTests.cs @@ -5,7 +5,6 @@ using Features.Users.Services; using Features.Users.Tests.TestSupport; using FluentAssertions; using Infrastructure.Configuration; -using Infrastructure.Jwt; using Microsoft.Extensions.Configuration; using Moq; diff --git a/web/backend/Features/Features.Users/DependencyInjection/FeaturesUsersServiceCollectionExtensions.cs b/web/backend/Features/Features.Users/DependencyInjection/FeaturesUsersServiceCollectionExtensions.cs index bd334afb..f8bac6b8 100644 --- a/web/backend/Features/Features.Users/DependencyInjection/FeaturesUsersServiceCollectionExtensions.cs +++ b/web/backend/Features/Features.Users/DependencyInjection/FeaturesUsersServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ public static class FeaturesUsersServiceCollectionExtensions public static IServiceCollection AddFeaturesUsers(this IServiceCollection services) { services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/web/backend/Features/Features.Users/Features.Users.csproj b/web/backend/Features/Features.Users/Features.Users.csproj index 692af470..6bbd0552 100644 --- a/web/backend/Features/Features.Users/Features.Users.csproj +++ b/web/backend/Features/Features.Users/Features.Users.csproj @@ -15,7 +15,6 @@ - @@ -24,5 +23,7 @@ + + diff --git a/web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs b/web/backend/Features/Features.Users/Services/ITokenInfrastructure.cs similarity index 94% rename from web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs rename to web/backend/Features/Features.Users/Services/ITokenInfrastructure.cs index fe7f5d5d..e4cf4d4c 100644 --- a/web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs +++ b/web/backend/Features/Features.Users/Services/ITokenInfrastructure.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -namespace Infrastructure.Jwt; +namespace Features.Users.Services; /// /// Service for generating and validating JSON Web Tokens (JWTs) used for authentication. diff --git a/web/backend/Infrastructure/Infrastructure.Jwt/JwtInfrastructure.cs b/web/backend/Features/Features.Users/Services/JwtInfrastructure.cs similarity index 98% rename from web/backend/Infrastructure/Infrastructure.Jwt/JwtInfrastructure.cs rename to web/backend/Features/Features.Users/Services/JwtInfrastructure.cs index 72815038..7a173a5e 100644 --- a/web/backend/Infrastructure/Infrastructure.Jwt/JwtInfrastructure.cs +++ b/web/backend/Features/Features.Users/Services/JwtInfrastructure.cs @@ -5,7 +5,7 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames; -namespace Infrastructure.Jwt; +namespace Features.Users.Services; /// /// Generates and validates HMAC-SHA256 signed JWTs using . diff --git a/web/backend/Features/Features.Users/Services/TokenService.cs b/web/backend/Features/Features.Users/Services/TokenService.cs index c738e2db..2566c8b3 100644 --- a/web/backend/Features/Features.Users/Services/TokenService.cs +++ b/web/backend/Features/Features.Users/Services/TokenService.cs @@ -3,7 +3,6 @@ using System.Security.Claims; using Domain.Exceptions; using Features.Users.Identity; using Infrastructure.Configuration; -using Infrastructure.Jwt; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; diff --git a/web/backend/Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj b/web/backend/Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj deleted file mode 100644 index d66c84f5..00000000 --- a/web/backend/Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - net10.0 - enable - enable - Infrastructure.Jwt - - - - - - - - - - -