//! 用户加载与状态判定 helper:service 层与 gRPC 层共用的唯一实现点。 //! gRPC 侧(`gaccount_helpers::store`)re-export 这些函数,避免 service 反向依赖传输层。 use crate::db::DbConn; use crate::errors::{AuthError, AuthResult}; use crate::models::users::user_mfa::MfaStatus as ModelMfaStatus; use crate::models::users::{user_mfa, users}; use chrono::Utc; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; pub const MAX_BATCH_USER_IDS: usize = 500; pub async fn load_user(db: &DbConn, user_id: i64) -> AuthResult { users::Entity::find_by_id(user_id) .one(db.read()) .await? .ok_or(AuthError::UserNotFound) } pub async fn load_active_user(db: &DbConn, user_id: i64) -> AuthResult { let user = load_user(db, user_id).await?; ensure_active(&user)?; Ok(user) } pub fn is_active(user: &users::Model) -> bool { user.deleted_at.is_none() && user.allow_use && !user.forbid_login } pub fn ensure_active(user: &users::Model) -> AuthResult<()> { if user.deleted_at.is_some() || !user.allow_use { return Err(AuthError::AccountUnavailable { reason: "account is not available".to_owned(), }); } if user.forbid_login && { match user.endtime_forbid_login_at { Some(until) => until > Utc::now(), None => true, } } { return Err(AuthError::LoginForbidden { reason: user.forbid_login_reason.clone(), }); } Ok(()) } pub fn ensure_security_version(user: &users::Model, expected: i32) -> AuthResult<()> { // `0` 是 proto 定义的「调用方无要求」;负数是没有意义的输入,按版本不符处理。 if expected < 0 { return Err(AuthError::SecurityVersionStale { current: user.security_version, }); } if expected > 0 && expected != user.security_version { return Err(AuthError::SecurityVersionStale { current: user.security_version, }); } Ok(()) } pub async fn active_mfa_count(db: &DbConn, user_id: i64) -> AuthResult { let factors = user_mfa::Entity::find() .filter(user_mfa::Column::UserId.eq(user_id)) .all(db.read()) .await?; Ok(factors .iter() .filter(|factor| { factor.mfa_status == ModelMfaStatus::Enabled && factor.revoked_at.is_none() }) .count() as u32) } pub async fn has_enabled_mfa(db: &DbConn, user_id: i64) -> AuthResult { Ok(active_mfa_count(db, user_id).await? > 0) }