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:
@@ -45,6 +45,11 @@ pub struct AdminsState {
|
||||
/// Capability gate: every endpoint here requires
|
||||
/// `InstanceManageUsers` (owner / admin).
|
||||
pub authz: Arc<dyn AuthzRepo>,
|
||||
/// Audit 2026-06-11 (PrincipalCache revocation-lag) — evicted on
|
||||
/// password change / deactivation so a just-revoked session or
|
||||
/// API key stops authenticating immediately instead of lingering
|
||||
/// for the cache TTL.
|
||||
pub principal_cache: Arc<crate::auth_middleware::PrincipalCache>,
|
||||
}
|
||||
|
||||
pub fn admins_router(state: AdminsState) -> Router {
|
||||
@@ -254,6 +259,12 @@ async fn patch_admin(
|
||||
tracing::error!(?err, "failed to expire api keys on password change");
|
||||
}
|
||||
}
|
||||
// Audit 2026-06-11 (PrincipalCache revocation-lag) — the DB
|
||||
// writes above are best-effort by design (a blip must not undo
|
||||
// the rotation), so evicting the in-process cache is what makes
|
||||
// the rotation take effect on the very next request rather than
|
||||
// after the cache TTL.
|
||||
state.principal_cache.evict_user(id);
|
||||
}
|
||||
|
||||
if let Some(email_patch) = input.email.as_ref() {
|
||||
@@ -323,6 +334,11 @@ async fn patch_admin(
|
||||
tracing::error!(?err, "failed to expire api keys for deactivated admin");
|
||||
}
|
||||
}
|
||||
// Audit 2026-06-11 (PrincipalCache revocation-lag) — evict
|
||||
// cached principals so a deactivated admin's session / API
|
||||
// keys stop authenticating immediately instead of lingering
|
||||
// for the cache TTL window.
|
||||
state.principal_cache.evict_user(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user