use crate::models::users::users; use crate::protos::auth::common::v1::{Code, PageResponse}; use crate::protos::auth::internal::v1::authority_service_server::{ AuthorityService, AuthorityServiceServer, }; use crate::protos::auth::internal::v1::{ AuthorityAccess, AuthorityUser, AuthorityUserPatch, BatchGetUserRequest, BatchGetUserResponse, CancelAccountDeletionRequest, CancelAccountDeletionResponse, DeleteAccountRequest, DeleteAccountResponse, GetSessionRequest, GetSessionResponse, GetUserRequest, GetUserResponse, ListUsersRequest, ListUsersResponse, PurgeAccountRequest, PurgeAccountResponse, UpdateUserRequest, UpdateUserResponse, ValidateRequest, ValidateResponse, }; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect}; use tonic::{Request, Response, Status}; use crate::AuthService; use crate::api::{context, convert, error, store}; use crate::errors::AuthError; pub struct AuthorityApi { svc: AuthService, } impl AuthorityApi { pub fn new(svc: AuthService) -> Self { Self { svc } } pub fn server( self, ) -> tonic::service::interceptor::InterceptedService< AuthorityServiceServer, context::Interceptor, > { let gate = context::Interceptor::new(self.svc.auth().rpc_key.clone()); AuthorityServiceServer::with_interceptor(self, gate) } } fn status(error_value: &AuthError, request_id: Option<&str>) -> Status { let code = match error_value { AuthError::Unauthenticated | AuthError::SecurityVersionStale { .. } => { Code::Unauthenticated } AuthError::Forbidden => Code::Forbidden, AuthError::UserNotFound => Code::UserNotFound, AuthError::SessionNotFound => Code::SessionNotFound, AuthError::MalformedRequest => Code::MalformedRequest, AuthError::VersionMismatch { .. } => Code::Conflict, _ => Code::Internal, }; error::status_of(code, error_value.message(), request_id) } fn user_status(error_value: &AuthError, request_id: Option<&str>) -> Status { match error_value { AuthError::UserNotFound => { error::status_of(Code::UserNotFound, "user not found", request_id) } other => status(other, request_id), } } fn access(user: &users::Model, mfa_verified: bool) -> AuthorityAccess { AuthorityAccess { id: user.id, username: user.username.clone(), allow_use: user.allow_use, forbid_login: user.forbid_login, endtime_forbid_login_at: convert::datetime_opt(user.endtime_forbid_login_at), is_admin: user.is_admin, security_version: user.security_version, deleted_at: convert::datetime_opt(user.deleted_at), mfa_verified, } } fn user(record: &users::Model) -> AuthorityUser { AuthorityUser { id: record.id, username: record.username.clone(), lowername: record.lowername.clone(), displayname: record.displayname.clone(), birth_date: record.birth_date.and_then(convert::date_timestamp), email: record.email.clone(), public_show_email: record.public_show_email, email_verified_at: convert::datetime_opt(record.email_verified_at), allow_use: record.allow_use, forbid_login: record.forbid_login, forbid_login_reason: record.forbid_login_reason.clone(), endtime_forbid_login_at: convert::datetime_opt(record.endtime_forbid_login_at), is_admin: record.is_admin, bot: record.bot, system: record.system, created_at: Some(convert::timestamp_from_datetime(record.created_at)), updated_at: Some(convert::timestamp_from_datetime(record.updated_at)), security_version: record.security_version, version: record.version, deleted_at: convert::datetime_opt(record.deleted_at), } } fn patch_value(patch: &AuthorityUserPatch) -> Result { if patch.displayname.is_some() && patch.clear_displayname { return Err("patch carries both displayname and clear_displayname".into()); } if let Some(username) = patch.username.as_deref() && username.trim().is_empty() { return Err("patch carries an empty username".into()); } if patch.username.is_none() && patch.displayname.is_none() && !patch.clear_displayname && patch.public_show_email.is_none() { return Err("patch carries no updatable field".into()); } let mut value = serde_json::json!({ "version": i64::from(patch.version) }); if let Some(username) = patch.username.as_deref() { value["username"] = serde_json::Value::String(username.to_owned()); } if patch.clear_displayname { value["displayname"] = serde_json::Value::Null; } else if let Some(displayname) = patch.displayname.as_deref() { value["displayname"] = serde_json::Value::String(displayname.to_owned()); } if let Some(show) = patch.public_show_email { value["public_show_email"] = serde_json::Value::Bool(show); } Ok(value) } #[tonic::async_trait] impl AuthorityService for AuthorityApi { async fn redeem_socket_ticket( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .redeem_socket_ticket_inner(meta.request_id(), request.into_inner()) .await; timer.finish("RedeemSocketTicket", &result); result } async fn validate( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .validate_inner(meta.request_id(), request.into_inner()) .await; timer.finish("Validate", &result); result } async fn get_user( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .get_user_inner(meta.request_id(), request.into_inner()) .await; timer.finish("GetUser", &result); result } async fn batch_get_user( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .batch_get_user_inner(meta.request_id(), request.into_inner()) .await; timer.finish("BatchGetUser", &result); result } async fn get_session( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .get_session_inner(meta.request_id(), request.into_inner()) .await; timer.finish("GetSession", &result); result } async fn update_user( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .update_user_inner(meta.request_id(), request.into_inner()) .await; timer.finish("UpdateUser", &result); result } async fn list_users( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .list_users_inner(meta.request_id(), request.into_inner()) .await; timer.finish("ListUsers", &result); result } async fn delete_account( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .delete_account_inner(meta.request_id(), request.into_inner()) .await; timer.finish("DeleteAccount", &result); result } async fn cancel_account_deletion( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .cancel_account_deletion_inner(meta.request_id(), request.into_inner()) .await; timer.finish("CancelAccountDeletion", &result); result } async fn purge_account( &self, request: Request, ) -> Result, Status> { let meta = context::meta(&request).await?; let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.internal.v1.AuthorityService"); let result = self .purge_account_inner(meta.request_id(), request.into_inner()) .await; timer.finish("PurgeAccount", &result); result } } impl AuthorityApi { #[tracing::instrument(skip_all, fields(request_id = %_request_id.unwrap_or_default()))] async fn redeem_socket_ticket_inner( &self, _request_id: Option<&str>, _payload: crate::protos::auth::internal::v1::RedeemSocketTicketRequest, ) -> Result, Status> { Err(Status::unimplemented("socket tickets are not available")) } #[tracing::instrument(skip_all, fields(request_id = %_request_id.unwrap_or_default()))] async fn validate_inner( &self, _request_id: Option<&str>, _payload: ValidateRequest, ) -> Result, Status> { Err(Status::unimplemented( "access token validation is not available", )) } #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))] async fn get_user_inner( &self, request_id: Option<&str>, payload: GetUserRequest, ) -> Result, Status> { let user_id = payload.user_id; if user_id <= 0 { return Err(error::invalid_argument( "user_id must be positive", request_id, )); } let record = self .svc .user(user_id) .await .map_err(|error_value| user_status(&error_value, request_id))?; Ok(Response::new(GetUserResponse { user: Some(user(&record)), })) } #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))] async fn batch_get_user_inner( &self, request_id: Option<&str>, payload: BatchGetUserRequest, ) -> Result, Status> { let user_ids = payload.user_ids; if user_ids.is_empty() { return Ok(Response::new(BatchGetUserResponse { users: Vec::new() })); } if user_ids.len() > crate::api::store::MAX_BATCH_USER_IDS { return Err(error::invalid_argument( "too many user ids in one batch", request_id, )); } let records = self .svc .users(&user_ids) .await .map_err(|error_value| user_status(&error_value, request_id))?; Ok(Response::new(BatchGetUserResponse { users: records.iter().map(user).collect(), })) } #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))] async fn get_session_inner( &self, request_id: Option<&str>, payload: GetSessionRequest, ) -> Result, Status> { if payload.user_id <= 0 { return Err(error::invalid_argument( "user_id must be positive", request_id, )); } if payload.version <= 0 { return Err(error::invalid_argument( "version must be positive", request_id, )); } if payload.session_id.is_empty() { return Err(error::invalid_argument( "session_id is required", request_id, )); } let (user, mfa_verified) = self .svc .check_session( payload.user_id, payload.version, &payload.session_id, request_id, ) .await .map_err(|error_value| status(&error_value, request_id))?; tracing::debug!( request_id, user_id = payload.user_id, "authority revalidated a session" ); Ok(Response::new(GetSessionResponse { access: Some(access(&user, mfa_verified)), })) } #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))] async fn update_user_inner( &self, request_id: Option<&str>, payload: UpdateUserRequest, ) -> Result, Status> { if payload.user_id <= 0 { return Err(error::invalid_argument( "user_id must be positive", request_id, )); } let patch = payload .patch .ok_or_else(|| error::invalid_argument("patch is required", request_id))?; if patch.version <= 0 { return Err(error::invalid_argument( "version must be positive", request_id, )); } let value = patch_value(&patch).map_err(|message| { error::invalid_argument(format!("invalid patch: {message}"), request_id) })?; let record = self.svc.update(payload.user_id, value).await.map_err( |error_value| match error_value { AuthError::VersionMismatch { .. } => { error::status_of(Code::Conflict, "account version conflict", request_id) } other => status(&other, request_id), }, )?; tracing::info!( request_id, user_id = record.id, version = record.version, "authority updated an account" ); Ok(Response::new(UpdateUserResponse { user: Some(user(&record)), })) } #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))] async fn list_users_inner( &self, request_id: Option<&str>, payload: ListUsersRequest, ) -> Result, Status> { let filter = payload.filter.unwrap_or_default(); if filter.user_ids.len() > crate::api::store::MAX_BATCH_USER_IDS { return Err(error::invalid_argument( "too many user ids in one filter", request_id, )); } let page = payload.page.unwrap_or_default(); let offset = store::parse_page_token(&page.page_token) .ok_or_else(|| error::invalid_argument("page_token is malformed", request_id))?; let page_size = convert::page_size(page.page_size); let query = order_by( users::Entity::find().filter(filter_condition(&filter)), payload.order, ); let rows = query .limit(page_size as u64) .offset(offset) .all(self.svc.db().read()) .await .map_err(|value| error::status(&crate::errors::AuthError::from(value), request_id))?; let next_page_token = if convert::has_more(offset, page_size, rows.len()) { store::page_token(offset + rows.len() as u64) } else { String::new() }; Ok(Response::new(ListUsersResponse { users: rows.iter().map(user).collect(), page: Some(PageResponse { next_page_token }), })) } async fn delete_account_inner( &self, request_id: Option<&str>, payload: DeleteAccountRequest, ) -> Result, Status> { if payload.user_id <= 0 { return Err(error::invalid_argument( "user_id must be positive", request_id, )); } self.svc .delete_account( payload.user_id, &payload.reason, request_id.unwrap_or_default(), ) .await .map_err(|failure| error::status(&failure, request_id))?; Ok(Response::new(DeleteAccountResponse {})) } async fn cancel_account_deletion_inner( &self, request_id: Option<&str>, payload: CancelAccountDeletionRequest, ) -> Result, Status> { if payload.user_id <= 0 { return Err(error::invalid_argument( "user_id must be positive", request_id, )); } self.svc .cancel_account_deletion(payload.user_id, request_id.unwrap_or_default()) .await .map_err(|failure| error::status(&failure, request_id))?; Ok(Response::new(CancelAccountDeletionResponse {})) } async fn purge_account_inner( &self, request_id: Option<&str>, payload: PurgeAccountRequest, ) -> Result, Status> { if payload.user_id <= 0 { return Err(error::invalid_argument( "user_id must be positive", request_id, )); } self.svc .purge_account(payload.user_id, request_id.unwrap_or_default()) .await .map_err(|failure| error::status(&failure, request_id))?; Ok(Response::new(PurgeAccountResponse {})) } } /// 把 proto 里的筛选条件翻成 SeaORM 条件树。未设置的字段一律跳过, /// 因此空 filter 等价于「全量列表」。 fn filter_condition( filter: &crate::protos::auth::internal::v1::ListUsersFilter, ) -> sea_orm::Condition { use crate::protos::auth::internal::v1::{UserKind, UserState}; use sea_orm::Condition; let mut any = Condition::all(); // 子串匹配统一走 LIKE:username / lowername / displayname / email 四列任一命中即可。 // 大小写不敏感靠 lowername 与 email 落库即小写来保证,displayname 则补一趟小写比较。 let needle = filter.query.trim(); if !needle.is_empty() { let lowered = needle.to_lowercase(); any = any.add( Condition::any() .add(users::Column::Username.contains(needle)) .add(users::Column::Lowername.contains(&lowered)) .add(users::Column::Displayname.contains(needle)) .add(users::Column::Email.contains(&lowered)), ); } if !filter.user_ids.is_empty() { any = any.add(users::Column::Id.is_in(filter.user_ids.iter().copied())); } // 形态筛选:system 优先于 bot,与 UserKind 的互斥语义一致。 match UserKind::try_from(filter.kind).ok() { Some(UserKind::Human) => { any = any .add(users::Column::Bot.eq(false)) .add(users::Column::System.eq(false)); } Some(UserKind::Bot) => { any = any .add(users::Column::Bot.eq(true)) .add(users::Column::System.eq(false)); } Some(UserKind::System) => { any = any.add(users::Column::System.eq(true)); } Some(UserKind::Unspecified) | None => {} } // 状态筛选:优先级 DELETED > LOGIN_FORBIDDEN > UNAVAILABLE > ACTIVE, // 与 UserState 的注释一致;DB 侧用同一顺序表达,避免一条记录落两档。 match UserState::try_from(filter.state).ok() { Some(UserState::Active) => { any = any .add(users::Column::DeletedAt.is_null()) .add(users::Column::AllowUse.eq(true)) .add(users::Column::ForbidLogin.eq(false)); } Some(UserState::Unavailable) => { any = any .add(users::Column::DeletedAt.is_null()) .add(users::Column::AllowUse.eq(false)); } Some(UserState::LoginForbidden) => { any = any .add(users::Column::DeletedAt.is_null()) .add(users::Column::ForbidLogin.eq(true)); } Some(UserState::Deleted) => { any = any.add(users::Column::DeletedAt.is_not_null()); } Some(UserState::Unspecified) | None => {} } if let Some(is_admin) = filter.is_admin { any = any.add(users::Column::IsAdmin.eq(is_admin)); } if let Some(verified) = filter.email_verified { any = if verified { any.add(users::Column::EmailVerifiedAt.is_not_null()) } else { any.add(users::Column::EmailVerifiedAt.is_null()) }; } any } fn order_by( mut query: sea_orm::Select, order: i32, ) -> sea_orm::Select { use crate::protos::auth::internal::v1::UserOrder; match UserOrder::try_from(order).ok() { Some(UserOrder::CreatedAtAsc) => query = query.order_by_asc(users::Column::CreatedAt), Some(UserOrder::LastLoginDesc) => { query = query .order_by_desc(users::Column::LastLoginAt) .order_by_desc(users::Column::Id); } Some(UserOrder::IdAsc) => query = query.order_by_asc(users::Column::Id), // 默认与 UNSPECIFIED 都走创建时间倒序;补 id 做稳定排序,翻页不跳行。 Some(UserOrder::CreatedAtDesc) | Some(UserOrder::Unspecified) | None => { query = query .order_by_desc(users::Column::CreatedAt) .order_by_desc(users::Column::Id); } } query }