use std::sync::Arc; use cache::redis::RedisError; use cache::RedisCache; use uuid::Uuid; /// Redis-backed deny-list for access tokens. /// /// Two levels of revocation: /// - per-token: `auth:deny:jti:{jti}` — a single access token is dead. /// - per-user: `auth:deny:user:{sub}` — all tokens for a user are dead /// (used on password change, suspension, full session revocation). #[derive(Clone)] pub struct AccessTokenBlacklist { kv: Arc, } impl AccessTokenBlacklist { pub fn new(kv: Arc) -> Self { Self { kv } } pub async fn revoke_jti(&self, jti: Uuid, ttl_seconds: u64) -> Result<(), RedisError> { let key = format!("auth:deny:jti:{}", jti.simple()); self.kv.set_bytes(&key, b"1".to_vec(), ttl_seconds).await } pub async fn revoke_user(&self, user_id: Uuid, ttl_seconds: u64) -> Result<(), RedisError> { let key = format!("auth:deny:user:{}", user_id.simple()); self.kv.set_bytes(&key, b"1".to_vec(), ttl_seconds).await } pub async fn is_revoked( &self, jti: Uuid, sub: Uuid, ) -> Result { let jti_key = format!("auth:deny:jti:{}", jti.simple()); let user_key = format!("auth:deny:user:{}", sub.simple()); let jti_hit = self.kv.get_bytes(&jti_key).await?.is_some(); if jti_hit { return Ok(true); } Ok(self.kv.get_bytes(&user_key).await?.is_some()) } }