fix(security): M2.19 review nits — idle/refresh config guard + adapter tests + dead-var/doc cleanup (#15)

- Add SecurityOptionsValidator (IValidateOptions<SecurityOptions>) enforcing
  RoleRefreshThresholdMinutes < IdleTimeoutMinutes; registered with ValidateOnStart in
  AddSecurity — startup FAILS if threshold >= idle, so the invariant cannot be silently
  misconfigured away.
- Update SecurityOptions XML-docs: class-level summary distinguishes JWT Bearer path
  (JwtSigningKey/JwtExpiryMinutes) from Blazor cookie session path (IdleTimeoutMinutes/
  RoleRefreshThresholdMinutes); both time fields document the ~45-min effective idle window
  and the new cross-field constraint.
- Remove dead jwtService variable from /auth/login lambda in AuthEndpoints.cs (resolved
  but never used since login moved to SessionClaimBuilder).
- Extract ApplyValidationResultAsync helper from OnValidatePrincipalAsync (pure
  decision-application step); add 3 adapter tests covering Reject → RejectPrincipal +
  SignOutAsync; Replace → ReplacePrincipal + ShouldRenew; Keep → no-op.
- Fix inaccurate TryRefreshAsync comment (dropped "OR last-activity needs advancing" —
  the code only returns non-null when roleRefreshDue).
- Add InternalsVisibleTo for Security.Tests in Security.csproj.
- Add IsRoleRefreshDue tests: missing claim → due; unparsable claim → due; plus integration
  test covering the full ValidateAsync path for a principal missing zb:lastrolerefresh
  (triggers refresh + re-stamps anchor rather than keeping stale principal forever).
- Add SecurityOptionsValidatorConfigGuardTests: default succeeds; equal fails; greater fails;
  boundary (idle-1) succeeds; wiring confirmed via AddSecurity container.
This commit is contained in:
Joseph Doherty
2026-06-16 08:12:11 -04:00
parent c7916d79a8
commit fddc69545f
6 changed files with 437 additions and 14 deletions
@@ -34,7 +34,6 @@ public static class AuthEndpoints
}
var ldapAuth = context.RequestServices.GetRequiredService<ILdapAuthService>();
var jwtService = context.RequestServices.GetRequiredService<JwtTokenService>();
var roleMapper = context.RequestServices.GetRequiredService<IGroupRoleMapper<string>>();
var authResult = await ldapAuth.AuthenticateAsync(username, password, context.RequestAborted);