fix(audit-2026-06-11/revocation-lag): evict PrincipalCache on credential change

The PrincipalCache (token-hash -> resolved Principal, 60s TTL) had no
eviction hook, so deactivation / password change / API-key revocation
flipped the DB rows but the cache kept serving the stale principal for
up to the TTL window. Closes the audit's PrincipalCache revocation-lag
Medium and greens authz::deactivating_user_revokes_their_api_keys.

- PrincipalCache::evict_user(user_id) + evict_token(hash).
- One shared cache Arc threaded into AuthState / AdminsState /
  ApiKeysState (a per-state cache would let the middleware's own copy
  keep authenticating a revoked principal).
- Evict on: deactivation, password change (both evict_user), logout
  (evict_token, precise), single API-key delete (evict_user).
- H-E1 note: DB-write invalidation stays best-effort by design (a blip
  must not undo the rotation); the cache evict is what makes it take
  effect on the next request.
- Renamed bearer_and_cookie_produce_same_principal ->
  session_token_and_api_key_produce_same_principal (cookie auth was
  removed in C-1; the test never tested cookies).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-12 18:12:54 +02:00
parent 19f3647f0e
commit 31ecfae86e
6 changed files with 116 additions and 4 deletions

View File

@@ -195,6 +195,11 @@ async fn logout(State(state): State<AuthState>, req: Request<Body>) -> Response
if let Err(err) = state.sessions.delete(&hash).await {
tracing::error!(?err, "admin_sessions delete failed");
}
// Audit 2026-06-11 (PrincipalCache revocation-lag) — drop just
// this token from the cache so the logged-out session can't keep
// authenticating for the cache TTL. Other sessions for the same
// user are left untouched.
state.principal_cache.evict_token(&hash);
}
StatusCode::NO_CONTENT.into_response()
}