Skip to content
Clone
e870254
75 lines 2.6 KB Rust
//! 用户级 access token 的签发核心:`token` 与 `app_token` 两条 HTTP 入口
//! 共用同一套会话校验、TTL 解析、用户加载与签名流程,仅响应外形不同。
use chrono::{DateTime, Utc};
use uuid::Uuid;
use crate::jwt::AppClaims;
use crate::server::AuthSvc;
use crate::server::errors::{AuthError, AuthResult};
use crate::server::share::{crypto, user as store};
use crate::session::Session;
pub struct IssuedToken {
pub token: String,
pub issued_at: i64,
pub expires_at: i64,
pub kid: String,
}
/// 校验会话归属并签发 AppClaims;`app_id` 为空表示不带应用绑定。
/// TTL 解析:`None | Some(0)` 用 `default_ttl`,超过 `max_ttl` 拒绝。
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip_all, fields(user_id = user_id, request_id = %request_id.unwrap_or_default()))]
pub async fn issue_user_claims(
svc: &AuthSvc,
session: &Session,
user_id: i64,
app_id: Option<String>,
audience: String,
ttl_seconds: Option<u32>,
default_ttl: u32,
max_ttl: u32,
request_id: Option<&str>,
) -> AuthResult<IssuedToken> {
if svc.require_session(session, request_id).await? != user_id {
return Err(AuthError::Unauthenticated);
}
let ttl = match ttl_seconds {
None | Some(0) => default_ttl,
Some(ttl) if ttl <= max_ttl => ttl,
Some(_) => return Err(AuthError::InvalidExpiry),
};
let user = store::load_active_user(svc.db(), user_id).await?;
let issued_at = crypto::now_secs() as i64;
let expires_at = issued_at + i64::from(ttl);
let claims = AppClaims {
is_admin: false,
issuer: svc.signing().issuer().to_owned(),
app_id: app_id.unwrap_or_default(),
user_id,
audience,
token_id: Uuid::now_v7().to_string(),
issued_at,
expires_at,
security_version: user.security_version,
session_id: session.get::<String>("auth_session_id")?,
mfa_verified: session.get::<bool>("mfa_verified")?.unwrap_or(false),
scope: None,
username: Some(user.username),
token_type: "access".to_owned(),
};
let token = svc.signing().sign(&claims)?;
Ok(IssuedToken {
token,
issued_at,
expires_at,
kid: svc.signing().kid().to_owned(),
})
}
/// Unix 秒转 `DateTime<Utc>`,越界视为内部错误。
pub fn timestamp(seconds: i64, label: &str) -> AuthResult<DateTime<Utc>> {
DateTime::<Utc>::from_timestamp(seconds, 0)
.ok_or_else(|| AuthError::internal(format!("{label} timestamp is out of range")))
}