08cefb1
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<RefreshResp, AuthError> {
let _timer = metrics::Timer::start(metrics::FEATURE_REFRESH);
let result: Result<RefreshResp, AuthError> = 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
}