Files
SIG-CM2.0/src/api/SIGCM2.Api/Program.cs

83 lines
2.3 KiB
C#
Raw Normal View History

using Microsoft.AspNetCore.Authorization;
using Serilog;
using Scalar.AspNetCore;
using SIGCM2.Api.Authorization;
feat(api): audit context middleware + scoped impl (UDT-010 B4) Wires the request-scoped audit context per design #D-2: Middleware pipeline in Program.cs: app.UseCors() app.UseMiddleware<CorrelationIdMiddleware>() // PRE-AUTH app.UseAuthentication() app.UseMiddleware<AuditActorMiddleware>() // POST-AUTH app.UseAuthorization() app.MapControllers() SIGCM2.Api/Middleware/CorrelationIdMiddleware.cs: - Preserves client-sent X-Correlation-Id header when a valid GUID, otherwise generates Guid.NewGuid(). Stores in HttpContext.Items (audit:correlationId). - Captures Ip (Connection.RemoteIpAddress) + UserAgent header into Items. - Echoes the correlation id back via response header (OnStarting + immediate set — immediate set makes unit testing against DefaultHttpContext reliable). SIGCM2.Api/Middleware/AuditActorMiddleware.cs: - Reads JWT 'sub' claim from authenticated HttpContext.User, parses to int, stores as audit:actorUserId. Anonymous / non-numeric sub leaves it unset. SIGCM2.Infrastructure/Audit/AuditContext.cs (IAuditContext scoped impl): - Reads Items entries via IHttpContextAccessor. Returns null / Guid.Empty when no HttpContext is available (jobs, tests without middleware). - ActorRoleId intentionally null for now — rol code → id resolution is deferred; the logger may resolve it at persist time in a later batch. DI registration (Infrastructure/DependencyInjection.cs): - services.AddScoped<IAuditContext, AuditContext>() Tests (Strict TDD): - CorrelationIdMiddlewareTests (6): generates/preserves/handles-malformed correlation id, sets response header, captures ip/ua, calls next. - AuditActorMiddlewareTests (5): authenticated/anonymous/no-sub/non-numeric/ calls-next. - AuditContextTests (7): reads from Items, null-http-context defaults, ActorRoleId currently null. Suite: 355/355 Application.Tests + 141/141 Api.Tests = 496/496 passing. Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-3/9, design#D-2, tasks#B4}
2026-04-16 13:32:13 -03:00
using SIGCM2.Api.Middleware;
using SIGCM2.Application;
using SIGCM2.Infrastructure;
using SIGCM2.Api.Filters;
// Bootstrap logger — before DI is built
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateBootstrapLogger();
Log.Information("Starting SIGCM2 API");
var builder = WebApplication.CreateBuilder(args);
// Serilog — reads from appsettings.json "Serilog" section
builder.Host.UseSerilog((ctx, lc) => lc
.ReadFrom.Configuration(ctx.Configuration));
// Application + Infrastructure DI
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);
// Authorization — handler lives in Api layer; DO NOT move to Infrastructure DI
builder.Services.AddAuthorization();
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, ForbiddenProblemDetailsHandler>();
// Controllers with exception filter
builder.Services.AddControllers(opts =>
{
opts.Filters.Add<ExceptionFilter>();
});
// OpenAPI / Scalar
builder.Services.AddOpenApi();
// CORS
var allowedOrigins = builder.Configuration
.GetSection("Cors:AllowedOrigins")
.Get<string[]>() ?? [];
builder.Services.AddCors(opts =>
{
opts.AddDefaultPolicy(policy =>
policy.WithOrigins(allowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod());
});
var app = builder.Build();
// Middleware pipeline
app.UseSerilogRequestLogging();
if (app.Environment.IsDevelopment() || app.Environment.IsEnvironment("Testing"))
{
app.MapOpenApi();
app.MapScalarApiReference(opts =>
{
opts.Title = "SIGCM2 API";
});
}
app.UseHttpsRedirection();
app.UseCors();
feat(api): audit context middleware + scoped impl (UDT-010 B4) Wires the request-scoped audit context per design #D-2: Middleware pipeline in Program.cs: app.UseCors() app.UseMiddleware<CorrelationIdMiddleware>() // PRE-AUTH app.UseAuthentication() app.UseMiddleware<AuditActorMiddleware>() // POST-AUTH app.UseAuthorization() app.MapControllers() SIGCM2.Api/Middleware/CorrelationIdMiddleware.cs: - Preserves client-sent X-Correlation-Id header when a valid GUID, otherwise generates Guid.NewGuid(). Stores in HttpContext.Items (audit:correlationId). - Captures Ip (Connection.RemoteIpAddress) + UserAgent header into Items. - Echoes the correlation id back via response header (OnStarting + immediate set — immediate set makes unit testing against DefaultHttpContext reliable). SIGCM2.Api/Middleware/AuditActorMiddleware.cs: - Reads JWT 'sub' claim from authenticated HttpContext.User, parses to int, stores as audit:actorUserId. Anonymous / non-numeric sub leaves it unset. SIGCM2.Infrastructure/Audit/AuditContext.cs (IAuditContext scoped impl): - Reads Items entries via IHttpContextAccessor. Returns null / Guid.Empty when no HttpContext is available (jobs, tests without middleware). - ActorRoleId intentionally null for now — rol code → id resolution is deferred; the logger may resolve it at persist time in a later batch. DI registration (Infrastructure/DependencyInjection.cs): - services.AddScoped<IAuditContext, AuditContext>() Tests (Strict TDD): - CorrelationIdMiddlewareTests (6): generates/preserves/handles-malformed correlation id, sets response header, captures ip/ua, calls next. - AuditActorMiddlewareTests (5): authenticated/anonymous/no-sub/non-numeric/ calls-next. - AuditContextTests (7): reads from Items, null-http-context defaults, ActorRoleId currently null. Suite: 355/355 Application.Tests + 141/141 Api.Tests = 496/496 passing. Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-3/9, design#D-2, tasks#B4}
2026-04-16 13:32:13 -03:00
// UDT-010: correlation id + ip/ua capture runs BEFORE auth so anonymous requests
// still get a correlation id and so logs can tie pre-auth events to the request.
app.UseMiddleware<CorrelationIdMiddleware>();
app.UseAuthentication();
feat(api): audit context middleware + scoped impl (UDT-010 B4) Wires the request-scoped audit context per design #D-2: Middleware pipeline in Program.cs: app.UseCors() app.UseMiddleware<CorrelationIdMiddleware>() // PRE-AUTH app.UseAuthentication() app.UseMiddleware<AuditActorMiddleware>() // POST-AUTH app.UseAuthorization() app.MapControllers() SIGCM2.Api/Middleware/CorrelationIdMiddleware.cs: - Preserves client-sent X-Correlation-Id header when a valid GUID, otherwise generates Guid.NewGuid(). Stores in HttpContext.Items (audit:correlationId). - Captures Ip (Connection.RemoteIpAddress) + UserAgent header into Items. - Echoes the correlation id back via response header (OnStarting + immediate set — immediate set makes unit testing against DefaultHttpContext reliable). SIGCM2.Api/Middleware/AuditActorMiddleware.cs: - Reads JWT 'sub' claim from authenticated HttpContext.User, parses to int, stores as audit:actorUserId. Anonymous / non-numeric sub leaves it unset. SIGCM2.Infrastructure/Audit/AuditContext.cs (IAuditContext scoped impl): - Reads Items entries via IHttpContextAccessor. Returns null / Guid.Empty when no HttpContext is available (jobs, tests without middleware). - ActorRoleId intentionally null for now — rol code → id resolution is deferred; the logger may resolve it at persist time in a later batch. DI registration (Infrastructure/DependencyInjection.cs): - services.AddScoped<IAuditContext, AuditContext>() Tests (Strict TDD): - CorrelationIdMiddlewareTests (6): generates/preserves/handles-malformed correlation id, sets response header, captures ip/ua, calls next. - AuditActorMiddlewareTests (5): authenticated/anonymous/no-sub/non-numeric/ calls-next. - AuditContextTests (7): reads from Items, null-http-context defaults, ActorRoleId currently null. Suite: 355/355 Application.Tests + 141/141 Api.Tests = 496/496 passing. Refs: sdd/udt-010-auditoria-trazabilidad/{spec#REQ-AUD-3/9, design#D-2, tasks#B4}
2026-04-16 13:32:13 -03:00
// UDT-010: actor extraction runs AFTER auth to read the JWT sub claim.
app.UseMiddleware<AuditActorMiddleware>();
app.UseAuthorization();
app.MapControllers();
app.Run();
// Exposed for WebApplicationFactory in integration tests
public partial class Program { }