Skip to content
Clone
a06673b
76 lines 2.5 KB Rust
use cache::redis::RedisError;
use jwt::error::JwtError;
use jwt::session;
use jwt::signer::{decode_access_token, decode_refresh_token, extract_kid};
use rate_limit::AccessTokenBlacklist;
use crate::error::AuthError;
use crate::features::AuthService;
use super::{LogoutCmd, LogoutResp};
fn map_redis(e: RedisError) -> AuthError {
AuthError::Internal(format!("redis: {e}"))
}
/// Revoke the session bound to a refresh token. Idempotent: an unknown token
/// yields `revoked = false` without error so that retries are safe.
///
/// When an access token is supplied (best-effort), its `jti` is also placed on
/// the deny-list so the already-issued, stateless access token stops validating
/// immediately instead of remaining live until expiry.
pub async fn logout(
cmd: LogoutCmd,
svc: &AuthService,
access_token: Option<String>,
) -> Result<LogoutResp, AuthError> {
let db = svc.db.writer();
let kid = extract_kid(&cmd.refresh_token).ok_or(JwtError::InvalidHeader)?;
let cache = svc.jwt.cache.read().await;
let cached = cache
.get(&kid)
.ok_or(JwtError::UnknownKid(kid.to_string()))?;
let _claims = decode_refresh_token(
&cmd.refresh_token,
&cached.decoding,
cached.algorithm,
&svc.jwt.settings.issuer,
&svc.jwt.settings.audience,
svc.jwt.settings.clock_skew_seconds,
)?;
drop(cache);
let token_hash = session::hash_token(&cmd.refresh_token);
let hit = session::find_session_by_hash(&db, &token_hash).await?;
let resp = match hit {
Some(sess) => {
session::revoke_session(&db, sess.id).await?;
LogoutResp { revoked: true }
}
None => LogoutResp { revoked: false },
};
// VULN-005: revoke the presented access token too (best-effort — an expired
// or malformed token is simply ignored).
if let Some(token) = access_token
&& let Some(akid) = extract_kid(&token)
&& let Some(acached) = svc.jwt.cache.read().await.get(&akid)
&& let Ok(claims) = decode_access_token(
&token,
&acached.decoding,
acached.algorithm,
&svc.jwt.settings.issuer,
&svc.jwt.settings.audience,
svc.jwt.settings.clock_skew_seconds,
)
{
let ttl = svc.jwt.settings.access_ttl.whole_seconds().max(1) as u64;
AccessTokenBlacklist::new(svc.kv.clone())
.revoke_jti(claims.jti, ttl)
.await
.map_err(map_redis)?;
}
Ok(resp)
}