use time::OffsetDateTime; use uuid::Uuid; use crate::error::JwtError; use crate::keys::CachedKey; /// Determine whether a key is still usable for verification. /// /// A key is usable if it is not hard-expired, and either it is active or it /// is still inside its grace window (rotating but not yet past grace_ends_at). pub fn is_key_usable_for_verify(key: &CachedKey, now: OffsetDateTime) -> bool { if key.expires_at.is_some_and(|exp| exp <= now) { return false; } if key.is_rotating { return key.grace_ends_at.map(|g| g > now).unwrap_or(false); } true } /// Reject a token when its key is no longer usable (grace window elapsed). pub fn ensure_key_usable(key: &CachedKey) -> Result<(), JwtError> { if is_key_usable_for_verify(key, OffsetDateTime::now_utc()) { Ok(()) } else { Err(JwtError::Expired) } } /// A token is replayed-forbidden once its parent session has a replacement /// that lies outside the grace window. pub fn reject_replay(grace_expires_at: Option, now: OffsetDateTime) -> bool { match grace_expires_at { Some(g) => g <= now, None => true, } } pub fn grace_deadline(started: OffsetDateTime, duration: time::Duration) -> OffsetDateTime { started + duration } pub fn kid_matches(kid: Uuid, expected: Uuid) -> bool { kid == expected }