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 crate::metrics::{self, JwtPairKind}; use super::{RefreshCmd, RefreshResp}; pub async fn refresh(cmd: RefreshCmd, svc: &AuthService) -> Result { let _timer = metrics::Timer::start(metrics::FEATURE_REFRESH); let result: Result = async { let db = svc.db.writer(); let cache = svc.jwt.cache.read().await; 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()))?; 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, }) } .await; metrics::record_outcome(metrics::FEATURE_REFRESH, &result); if result.is_ok() { metrics::record_jwt_pair_issued(JwtPairKind::Refresh); } result }