feat(auth): Program.cs integration for Audit Logging (AEG-AUTH-001 Part 2)

- Register IAuthAuditSql (AuthAuditSql) in DI
- Add AuthAuditMiddleware to pipeline after authentication
- Fix AuthAuditSql using statement + NpgsqlInet construction
- Fix GetAuditLogsEndpoint response mapping (AuthAuditLogEntry → AuditLogItem)
- Build verified (Release mode, 0 errors)

Implements audit trail for all /api/auth/* endpoints:
- Captures event type, status, IP, user agent, endpoint, error details
- Immutable append-only storage with compliance views
- Async non-blocking logging with graceful failure handling
- Ready for 0047 migration application and testing

WBS: AEG-X-005 (JWT/OIDC/fail-closed) + AEG-AUTH-001 (Audit)
Gate: G3 (Shadow Run API)
Status: CODE_COMPLETE → MIGRATION_READY

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 01:46:25 +09:00
parent 3a5f893b2a
commit f20d19cf4b
3 changed files with 26 additions and 3 deletions
@@ -1,6 +1,7 @@
using Dapper; using Dapper;
using System.Data; using System.Data;
using NpgsqlTypes; using NpgsqlTypes;
using KArtSell.BuildingBlocks.Data;
namespace KArtSell.Host.Features.Audit; namespace KArtSell.Host.Features.Audit;
@@ -46,7 +47,7 @@ public sealed class AuthAuditSql : IAuthAuditSql
entry.IdentityId, entry.IdentityId,
entry.Username, entry.Username,
entry.Role, entry.Role,
IpAddress = entry.IpAddress != null ? NpgsqlInet.Parse(entry.IpAddress) : (NpgsqlInet?)null, IpAddress = entry.IpAddress != null ? new NpgsqlInet(entry.IpAddress) : (NpgsqlInet?)null,
entry.UserAgent, entry.UserAgent,
entry.Endpoint, entry.Endpoint,
entry.HttpMethod, entry.HttpMethod,
@@ -47,13 +47,29 @@ public sealed class GetAuditLogsEndpoint : Endpoint<GetAuditLogsRequest, GetAudi
ct: ct ct: ct
); );
var items = logs.Select(entry => new AuditLogItem
{
AuditId = entry.AuditId,
EventType = entry.EventType,
IdentityId = entry.IdentityId,
Username = entry.Username,
Role = entry.Role,
IpAddress = entry.IpAddress,
Endpoint = entry.Endpoint,
HttpMethod = entry.HttpMethod,
Status = entry.Status,
ErrorCode = entry.ErrorCode,
ErrorMessage = entry.ErrorMessage,
OccurredAt = entry.OccurredAt
}).ToList();
await Send.OkAsync(new GetAuditLogsResponse await Send.OkAsync(new GetAuditLogsResponse
{ {
Items = logs.ToList(), Items = items,
Total = total, Total = total,
Page = req.Page, Page = req.Page,
PageSize = req.PageSize PageSize = req.PageSize
}, 200, ct); }, ct);
} }
} }
+6
View File
@@ -9,6 +9,8 @@ using KArtSell.Host.Infrastructure;
using KArtSell.Host.OpenApi; using KArtSell.Host.OpenApi;
using KArtSell.Host.Observability; using KArtSell.Host.Observability;
using KArtSell.Host.Features.Observability; using KArtSell.Host.Features.Observability;
using KArtSell.Host.Features.Audit;
using KArtSell.Host.Middleware;
using KArtSell.BuildingBlocks.Data; using KArtSell.BuildingBlocks.Data;
using KArtSell.BuildingBlocks.Reliability; using KArtSell.BuildingBlocks.Reliability;
using KArtSell.BuildingBlocks.Time; using KArtSell.BuildingBlocks.Time;
@@ -233,6 +235,9 @@ builder.Services.AddScoped<KArtSell.Modules.ModelOperations.Compliance.LogAuditE
builder.Services.AddScoped<KArtSell.Modules.ModelOperations.Compliance.ProcessGdprRequestHandler>(); builder.Services.AddScoped<KArtSell.Modules.ModelOperations.Compliance.ProcessGdprRequestHandler>();
builder.Services.AddScoped<KArtSell.Modules.ModelOperations.Compliance.GdprRedactionJob>(); builder.Services.AddScoped<KArtSell.Modules.ModelOperations.Compliance.GdprRedactionJob>();
// Authentication Audit Logging (AEG-AUTH-001)
builder.Services.AddScoped<IAuthAuditSql, AuthAuditSql>();
builder.Services.AddProblemDetails(); builder.Services.AddProblemDetails();
const string authenticationScheme = "KArtSell"; const string authenticationScheme = "KArtSell";
@@ -342,6 +347,7 @@ if (app.Environment.IsDevelopment())
} }
app.UseAuthentication(); app.UseAuthentication();
app.UseMiddleware<AuthAuditMiddleware>();
app.UseAuthorization(); app.UseAuthorization();
app.UseFastEndpoints(config => config.Endpoints.RoutePrefix = "api"); app.UseFastEndpoints(config => config.Endpoints.RoutePrefix = "api");
app.MapHub<KArtSell.Host.Consumers.ShadowRunHub>("/api/hubs/shadow-run"); app.MapHub<KArtSell.Host.Consumers.ShadowRunHub>("/api/hubs/shadow-run");