Move Infrastructure.Jwt into Features.Users (#287)

This commit is contained in:
Aaron Po
2026-09-11 23:14:30 -04:00
committed by GitHub
parent 5fa78fbdf6
commit 7187631ebc
19 changed files with 19 additions and 50 deletions

View File

@@ -89,7 +89,6 @@ flowchart TB
subgraph INFRA["Infrastructure Layer"] subgraph INFRA["Infrastructure Layer"]
direction LR direction LR
I1["Database.Connection"] I1["Database.Connection"]
I2["Infrastructure.Jwt"]
I3["Infrastructure.PasswordHashing"] I3["Infrastructure.PasswordHashing"]
I4["Infrastructure.Email /<br/>Email.Templates"] I4["Infrastructure.Email /<br/>Email.Templates"]
I5["Infrastructure.FileUpload"] I5["Infrastructure.FileUpload"]
@@ -133,7 +132,6 @@ own
- Every `Features.*` project (for controller/MediatR discovery) - Every `Features.*` project (for controller/MediatR discovery)
- `Shared.Contracts`, `Shared.Application` - `Shared.Contracts`, `Shared.Application`
- `Infrastructure.Jwt` (for the auth middleware)
**Rules**: **Rules**:
@@ -167,10 +165,9 @@ brewery photo upload command) rather than bound to an HTTP route directly.
- `Domain.Entities`, `Domain.Exceptions` - `Domain.Entities`, `Domain.Exceptions`
- `Database.Connection` (generic ADO.NET connection/command plumbing) plus - `Database.Connection` (generic ADO.NET connection/command plumbing) plus
whichever infrastructure project the slice needs (`Infrastructure.Jwt`/ whichever infrastructure project the slice needs (`Infrastructure.PasswordHashing`
`Infrastructure.PasswordHashing` for Users, `Infrastructure.Email`/ for Users, `Infrastructure.Email`/`Infrastructure.Email.Templates` for
`Infrastructure.Email.Templates` for Emails, `Infrastructure.FileUpload` for Emails, `Infrastructure.FileUpload` for PhotoUpload)
PhotoUpload)
- `Shared.Contracts`, `Shared.Application` - `Shared.Contracts`, `Shared.Application`
- No other `Features.*` project, with two exceptions: `Features.Users` - No other `Features.*` project, with two exceptions: `Features.Users`
references `Features.PhotoUpload` directly for avatar uploads, and references `Features.PhotoUpload` directly for avatar uploads, and
@@ -210,7 +207,6 @@ whichever slices need them
| Project | Description | | 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. | | `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.PasswordHashing` | Argon2id password hashing. |
| `Infrastructure.Email` | Email sending capabilities (SMTP/MailKit). | | `Infrastructure.Email` | Email sending capabilities (SMTP/MailKit). |
| `Infrastructure.Email.Templates` | Email template rendering (Razor components). | | `Infrastructure.Email.Templates` | Email template rendering (Razor components). |

View File

@@ -97,7 +97,7 @@ see the notes for what else each of those foundation projects pulls in.
- **Omitted from the diagram**: - **Omitted from the diagram**:
- `Domain.Exceptions` (referenced by nearly every project directly, including - `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 - `Infrastructure.Configuration` (the shared options-binding project
referenced by `Database.Connection`, `Infrastructure.Email`, referenced by `Database.Connection`, `Infrastructure.Email`,
`Infrastructure.FileUpload`, and `Features.Emails` directly) `Infrastructure.FileUpload`, and `Features.Emails` directly)

View File

@@ -23,7 +23,6 @@ end
subgraph INFRA["Slice-specific Infrastructure"] subgraph INFRA["Slice-specific Infrastructure"]
direction LR direction LR
JWT["Infrastructure.Jwt"]
PwdHash["Infrastructure.PasswordHashing"] PwdHash["Infrastructure.PasswordHashing"]
FileUpload["Infrastructure.FileUpload"] FileUpload["Infrastructure.FileUpload"]
Email["Infrastructure.Email"] Email["Infrastructure.Email"]
@@ -36,6 +35,6 @@ BrewerySlice --> FOUNDATION
LocationsSlice --> FOUNDATION LocationsSlice --> FOUNDATION
BeersSlice --> FOUNDATION BeersSlice --> FOUNDATION
PhotoSlice --> FOUNDATION & FileUpload PhotoSlice --> FOUNDATION & FileUpload
UsersSlice --> FOUNDATION & JWT & PwdHash UsersSlice --> FOUNDATION & PwdHash
EmailsSlice --> Email & EmailTemplates EmailsSlice --> Email & EmailTemplates
DBConn -- Dapper SQL, no ORM --> Tables[("SQL Server<br>Tables")] DBConn -- Dapper SQL, no ORM --> Tables[("SQL Server<br>Tables")]

View File

@@ -22,7 +22,7 @@ The Biergarten API validates JSON Web Tokens (JWTs) across three token types:
### Infrastructure layer ### Infrastructure layer
#### [ITokenInfrastructure](../../web/backend/Infrastructure/Infrastructure.Jwt/ITokenInfrastructure.cs) #### [ITokenInfrastructure](../../web/backend/Features/Features.Users/Services/ITokenInfrastructure.cs)
Low-level JWT operations. Low-level JWT operations.
@@ -32,7 +32,7 @@ Low-level JWT operations.
- `ValidateJwtAsync()` - Validates token signature, expiration, and format - `ValidateJwtAsync()` - Validates token signature, expiration, and format
**Implementation:** **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 - Uses Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler
- Algorithm: HS256 (HMAC-SHA256) - Algorithm: HS256 (HMAC-SHA256)
@@ -76,12 +76,11 @@ directly, bypassing the `Features.Users` slice entirely.
- Extracts and validates claims (Sub, UniqueName) - Extracts and validates claims (Sub, UniqueName)
- Throws `UnauthorizedException` on validation failure - Throws `UnauthorizedException` on validation failure
`TokenService` is registered by `Features.Users`' own `AddFeaturesUsers()` `TokenService` and the lower-level `ITokenInfrastructure` (JWT
extension method, except for the lower-level `ITokenInfrastructure` (JWT signing/verification) it depends on are both registered by `Features.Users`'
signing/verification) it depends on, which is registered by the host own `AddFeaturesUsers()` extension method. `JwtAuthenticationHandler`
(`API.Core/ServiceCollectionExtensions.cs`, via `AddCoreInfrastructure()`, (host-level auth middleware) also depends on `ITokenInfrastructure` directly,
called from `Program.cs`) since `JwtAuthenticationHandler` (host-level auth resolving it from the same DI container.
middleware) also depends on it directly.
### Integration points ### Integration points

View File

@@ -23,7 +23,6 @@
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" /> <ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" /> <ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" /> <ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.FileUpload\Infrastructure.FileUpload.csproj" /> <ProjectReference Include="..\..\Infrastructure\Infrastructure.FileUpload\Infrastructure.FileUpload.csproj" />
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" /> <ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
<ProjectReference Include="..\..\Features\Features.Users\Features.Users.csproj" /> <ProjectReference Include="..\..\Features\Features.Users\Features.Users.csproj" />

View File

@@ -1,7 +1,7 @@
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Features.Users.Services;
using Infrastructure.Configuration; using Infrastructure.Configuration;
using Infrastructure.Jwt;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;

View File

@@ -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.Configuration/Infrastructure.Configuration.csproj", "Infrastructure/Infrastructure.Configuration/"]
COPY ["Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj", "Infrastructure/Infrastructure.Email/"] COPY ["Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj", "Infrastructure/Infrastructure.Email/"]
COPY ["Infrastructure/Infrastructure.FileUpload/Infrastructure.FileUpload.csproj", "Infrastructure/Infrastructure.FileUpload/"] 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.Application/Shared.Application.csproj", "Shared/Shared.Application/"]
COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"]
RUN dotnet restore "Biergarten.API.slnx" RUN dotnet restore "Biergarten.API.slnx"

View File

@@ -13,7 +13,6 @@ using Features.Users.DependencyInjection;
using FluentValidation; using FluentValidation;
using FluentValidation.AspNetCore; using FluentValidation.AspNetCore;
using Infrastructure.FileUpload; using Infrastructure.FileUpload;
using Infrastructure.Jwt;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Shared.Application.Behaviors; using Shared.Application.Behaviors;
@@ -105,8 +104,6 @@ internal static class ServiceCollectionExtensions
public IServiceCollection AddCoreInfrastructure() => public IServiceCollection AddCoreInfrastructure() =>
services services
// ITokenInfrastructure is registered here because JwtAuthenticationHandler depends on it directly.
.AddScoped<ITokenInfrastructure, JwtInfrastructure>()
.AddSingleton<IFileStorageProvider, S3FileStorageProvider>() .AddSingleton<IFileStorageProvider, S3FileStorageProvider>()
.AddScoped<GlobalExceptionFilter>(); .AddScoped<GlobalExceptionFilter>();

View File

@@ -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.Configuration/Infrastructure.Configuration.csproj", "Infrastructure/Infrastructure.Configuration/"]
COPY ["Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj", "Infrastructure/Infrastructure.Email/"] COPY ["Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj", "Infrastructure/Infrastructure.Email/"]
COPY ["Infrastructure/Infrastructure.FileUpload/Infrastructure.FileUpload.csproj", "Infrastructure/Infrastructure.FileUpload/"] 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.Application/Shared.Application.csproj", "Shared/Shared.Application/"]
COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"]
RUN dotnet restore "Biergarten.API.slnx" RUN dotnet restore "Biergarten.API.slnx"

View File

@@ -1,8 +1,8 @@
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Features.Users.Services;
using FluentAssertions; using FluentAssertions;
using Infrastructure.Configuration; using Infrastructure.Configuration;
using Infrastructure.Jwt;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Reqnroll; using Reqnroll;

View File

@@ -23,7 +23,6 @@
<Project Path="Infrastructure/Infrastructure.Configuration/Infrastructure.Configuration.csproj" /> <Project Path="Infrastructure/Infrastructure.Configuration/Infrastructure.Configuration.csproj" />
<Project Path="Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj" /> <Project Path="Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj" />
<Project Path="Infrastructure/Infrastructure.FileUpload/Infrastructure.FileUpload.csproj" /> <Project Path="Infrastructure/Infrastructure.FileUpload/Infrastructure.FileUpload.csproj" />
<Project Path="Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj" />
</Folder> </Folder>
<Folder Name="/Features/" /> <Folder Name="/Features/" />
<Folder Name="/Features/Users/"> <Folder Name="/Features/Users/">

View File

@@ -6,7 +6,6 @@ using Features.Users.Services;
using Features.Users.Tests.TestSupport; using Features.Users.Tests.TestSupport;
using FluentAssertions; using FluentAssertions;
using Infrastructure.Configuration; using Infrastructure.Configuration;
using Infrastructure.Jwt;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Moq; using Moq;

View File

@@ -5,7 +5,6 @@ using Features.Users.Services;
using Features.Users.Tests.TestSupport; using Features.Users.Tests.TestSupport;
using FluentAssertions; using FluentAssertions;
using Infrastructure.Configuration; using Infrastructure.Configuration;
using Infrastructure.Jwt;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Moq; using Moq;

View File

@@ -18,6 +18,7 @@ public static class FeaturesUsersServiceCollectionExtensions
public static IServiceCollection AddFeaturesUsers(this IServiceCollection services) public static IServiceCollection AddFeaturesUsers(this IServiceCollection services)
{ {
services.AddScoped<ITokenService, TokenService>(); services.AddScoped<ITokenService, TokenService>();
services.AddScoped<ITokenInfrastructure, JwtInfrastructure>();
services.AddScoped<IPasswordInfrastructure, Argon2Infrastructure>(); services.AddScoped<IPasswordInfrastructure, Argon2Infrastructure>();
services.AddScoped<IUserListRepository, UserListRepository>(); services.AddScoped<IUserListRepository, UserListRepository>();
services.AddScoped<IUserProfileRepository, UserProfileRepository>(); services.AddScoped<IUserProfileRepository, UserProfileRepository>();

View File

@@ -15,7 +15,6 @@
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" /> <ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" /> <ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" /> <ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" />
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" /> <ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
<ProjectReference Include="..\Features.PhotoUpload\Features.PhotoUpload.csproj" /> <ProjectReference Include="..\Features.PhotoUpload\Features.PhotoUpload.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" /> <ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
@@ -24,5 +23,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" /> <PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.16.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,6 +1,6 @@
using System.Security.Claims; using System.Security.Claims;
namespace Infrastructure.Jwt; namespace Features.Users.Services;
/// <summary> /// <summary>
/// Service for generating and validating JSON Web Tokens (JWTs) used for authentication. /// Service for generating and validating JSON Web Tokens (JWTs) used for authentication.

View File

@@ -5,7 +5,7 @@ using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames; using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
namespace Infrastructure.Jwt; namespace Features.Users.Services;
/// <summary> /// <summary>
/// Generates and validates HMAC-SHA256 signed JWTs using <see cref="JsonWebTokenHandler" />. /// Generates and validates HMAC-SHA256 signed JWTs using <see cref="JsonWebTokenHandler" />.

View File

@@ -3,7 +3,6 @@ using System.Security.Claims;
using Domain.Exceptions; using Domain.Exceptions;
using Features.Users.Identity; using Features.Users.Identity;
using Infrastructure.Configuration; using Infrastructure.Configuration;
using Infrastructure.Jwt;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Infrastructure.Jwt</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
</ItemGroup>
</Project>