Resolve all high-severity architecture review findings

This commit is contained in:
Joseph Doherty
2026-09-09 16:13:58 -04:00
parent 8bfe65512e
commit 146a13238f
192 changed files with 3160 additions and 753 deletions
@@ -68,6 +68,7 @@ public static class ServiceCollectionExtensions
// is the wall clock. TryAddSingleton keeps the Host free to register its own.
services.TryAddSingleton(TimeProvider.System);
services.AddScoped<CookieSessionValidator>();
services.AddSingleton<SessionActivityTracker>();
// Audit Actor wiring (Phase 3): the user-facing inbound API audit path
// sources AuditEvent.Actor from the authenticated principal via this
@@ -157,6 +158,11 @@ public static class ServiceCollectionExtensions
// throws — CookieSessionValidator.ValidateAsync swallows refresh faults and
// keeps the session (mirrors "LDAP failure: active sessions continue").
options.Events.OnValidatePrincipal = OnValidatePrincipalAsync;
options.Events.OnCheckSlidingExpiration = context =>
{
if (IsPassiveSessionRequest(context.HttpContext)) context.ShouldRenew = false;
return Task.CompletedTask;
};
});
}
@@ -218,13 +224,19 @@ public static class ServiceCollectionExtensions
return services;
}
/// <summary>Requests that cannot by themselves establish user activity.</summary>
internal static bool IsPassiveSessionRequest(HttpContext context)
=> context.Request.Path.StartsWithSegments("/auth/ping")
|| context.Request.Path.StartsWithSegments("/auth/activity")
|| context.Request.Path.StartsWithSegments("/_blazor");
/// <summary>
/// The thin <see cref="CookieAuthenticationEvents.OnValidatePrincipal"/>
/// adapter. It resolves the request-scoped <see cref="CookieSessionValidator"/>,
/// asks it for a decision, and applies it to the cookie context:
/// <list type="bullet">
/// <item><see cref="SessionValidationAction.Reject"/> → <see cref="CookieValidatePrincipalContext.RejectPrincipal"/> + sign out (idle-timeout — the only sign-out path).</item>
/// <item><see cref="SessionValidationAction.Replace"/> → <see cref="CookieValidatePrincipalContext.ReplacePrincipal"/> + <c>ShouldRenew = true</c> (role mapping refreshed).</item>
/// <item><see cref="SessionValidationAction.Replace"/> → <see cref="CookieValidatePrincipalContext.ReplacePrincipal"/> + renewal only for non-passive requests (role mapping refreshed).</item>
/// <item><see cref="SessionValidationAction.Keep"/> → no-op (no refresh due, or a swallowed refresh fault).</item>
/// </list>
/// All logic lives in <see cref="CookieSessionValidator.ValidateAsync"/>, which never
@@ -237,7 +249,7 @@ public static class ServiceCollectionExtensions
var validator = context.HttpContext.RequestServices.GetRequiredService<CookieSessionValidator>();
var result = await validator
.ValidateAsync(context.Principal, context.HttpContext.RequestAborted)
.ValidateAsync(context.Principal, context.HttpContext.RequestAborted, recordActivity: !IsPassiveSessionRequest(context.HttpContext))
.ConfigureAwait(false);
await ApplyValidationResultAsync(context, result).ConfigureAwait(false);
@@ -272,7 +284,7 @@ public static class ServiceCollectionExtensions
// Role mapping refreshed from stored groups — swap in the rebuilt
// principal and re-issue the cookie so the new claims persist.
context.ReplacePrincipal(result.Principal);
context.ShouldRenew = true;
context.ShouldRenew = !IsPassiveSessionRequest(context.HttpContext);
break;
case SessionValidationAction.Keep: