use db::conn::sea_orm::EntityTrait; use db::user::users::Entity as UserEntity; use jwt::handlers::TokenPairResponse; use jwt::rotation::{RotationContext, UserSnapshot, rotate_refresh}; use jwt::signer::{decode_refresh_token, extract_kid}; use crate::error::AuthError; use crate::features::AuthService; use super::{RefreshCmd, RefreshResp}; pub async fn refresh(cmd: RefreshCmd, svc: &AuthService) -> Result { let db = svc.db.writer(); let cache = svc.jwt.cache.read().await; // VULN-007: decode the refresh token to learn user_id, then look up // the live passwd_version so rotate_refresh can reject a token whose // claim lags behind a password reset. let user_id = { let kid = extract_kid(&cmd.refresh_token) .ok_or_else(|| AuthError::Unauthorized("invalid refresh token".into()))?; let cached = cache .get(&kid) .ok_or_else(|| AuthError::Unauthorized("unknown signing key".into()))?; 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, ) .map_err(AuthError::Jwt)?; claims.sub }; let user = UserEntity::find_by_id(user_id) .one(&db) .await? .ok_or_else(|| AuthError::Unauthorized("unknown user".into()))?; // VULN-002/B4: suspended / restricted / deactivated accounts must not be // able to keep their session alive through refresh. Mirror the login gate // so a suspended user cannot mint fresh token pairs indefinitely. if user.is_restricted { return Err(AuthError::Forbidden("account restricted".into())); } if user.prohibit_login { return Err(AuthError::Forbidden("login prohibited".into())); } if !user.is_active { return Err(AuthError::Forbidden( "account inactive, please verify your email".into(), )); } let current_pwd_version = user.passwd_version.unwrap_or(0); let ctx = RotationContext { db: &db, settings: &svc.jwt.settings, cache: &cache, }; let snapshot = UserSnapshot { pwd_version: current_pwd_version, username: user.username.clone(), is_admin: user.is_admin, is_restricted: user.is_restricted, }; let pair: TokenPairResponse = rotate_refresh(&ctx, &cmd.refresh_token, &snapshot, cmd.app_id) .await? .into(); Ok(RefreshResp { access_token: pair.access_token, refresh_token: pair.refresh_token, token_type: pair.token_type, expires_in: pair.expires_in, }) }