- 拆分 refresh token 限流为独立中间件(30次/分 vs 登录5次/分) - 修复积分 recent-activity 500:JOIN 通过 points_account 中间表 - 修复患者/医生不存在返回 400 → 正确的 404 NotFound
373 lines
13 KiB
Rust
373 lines
13 KiB
Rust
use axum::Router;
|
||
use uuid::Uuid;
|
||
|
||
use erp_core::error::AppResult;
|
||
use erp_core::events::EventBus;
|
||
use erp_core::module::{ErpModule, PermissionDescriptor};
|
||
|
||
use crate::handler::{auth_handler, org_handler, role_handler, user_handler, wechat_handler};
|
||
|
||
/// Auth module implementing the `ErpModule` trait.
|
||
///
|
||
/// Manages identity, authentication, and user CRUD within the ERP platform.
|
||
/// This module has no dependencies on other business modules.
|
||
pub struct AuthModule;
|
||
|
||
impl AuthModule {
|
||
pub fn new() -> Self {
|
||
Self
|
||
}
|
||
|
||
/// Build public (unauthenticated) routes for the auth module.
|
||
///
|
||
/// These routes do not require a valid JWT token.
|
||
/// The caller wraps this into whatever state type the application uses.
|
||
pub fn public_routes<S>() -> Router<S>
|
||
where
|
||
crate::auth_state::AuthState: axum::extract::FromRef<S>,
|
||
S: Clone + Send + Sync + 'static,
|
||
{
|
||
Router::new()
|
||
.route("/auth/login", axum::routing::post(auth_handler::login))
|
||
.route(
|
||
"/auth/wechat/login",
|
||
axum::routing::post(wechat_handler::wechat_login),
|
||
)
|
||
.route(
|
||
"/auth/wechat/bind-phone",
|
||
axum::routing::post(wechat_handler::wechat_bind_phone),
|
||
)
|
||
}
|
||
|
||
/// Refresh token routes — public but with higher rate limit (30/min vs 5/min for login).
|
||
pub fn refresh_routes<S>() -> Router<S>
|
||
where
|
||
crate::auth_state::AuthState: axum::extract::FromRef<S>,
|
||
S: Clone + Send + Sync + 'static,
|
||
{
|
||
Router::new().route("/auth/refresh", axum::routing::post(auth_handler::refresh))
|
||
}
|
||
|
||
/// Build protected (authenticated) routes for the auth module.
|
||
///
|
||
/// These routes require a valid JWT token, verified by the middleware layer.
|
||
/// The caller wraps this into whatever state type the application uses.
|
||
pub fn protected_routes<S>() -> Router<S>
|
||
where
|
||
crate::auth_state::AuthState: axum::extract::FromRef<S>,
|
||
S: Clone + Send + Sync + 'static,
|
||
{
|
||
Router::new()
|
||
.route("/auth/logout", axum::routing::post(auth_handler::logout))
|
||
.route(
|
||
"/auth/change-password",
|
||
axum::routing::post(auth_handler::change_password),
|
||
)
|
||
.route(
|
||
"/users",
|
||
axum::routing::get(user_handler::list_users).post(user_handler::create_user),
|
||
)
|
||
.route(
|
||
"/users/{id}",
|
||
axum::routing::get(user_handler::get_user)
|
||
.put(user_handler::update_user)
|
||
.delete(user_handler::delete_user),
|
||
)
|
||
.route(
|
||
"/users/{id}/roles",
|
||
axum::routing::post(user_handler::assign_roles),
|
||
)
|
||
.route(
|
||
"/users/{id}/reset-password",
|
||
axum::routing::post(user_handler::reset_password),
|
||
)
|
||
.route(
|
||
"/roles",
|
||
axum::routing::get(role_handler::list_roles).post(role_handler::create_role),
|
||
)
|
||
// 精确匹配 /roles/permissions,必须在 /roles/{id} 之前注册
|
||
.route(
|
||
"/roles/permissions",
|
||
axum::routing::get(role_handler::list_permissions),
|
||
)
|
||
.route(
|
||
"/roles/{id}",
|
||
axum::routing::get(role_handler::get_role)
|
||
.put(role_handler::update_role)
|
||
.delete(role_handler::delete_role),
|
||
)
|
||
.route(
|
||
"/roles/{id}/permissions",
|
||
axum::routing::get(role_handler::get_role_permissions)
|
||
.post(role_handler::assign_permissions),
|
||
)
|
||
.route(
|
||
"/permissions",
|
||
axum::routing::get(role_handler::list_permissions),
|
||
)
|
||
// Organization routes
|
||
.route(
|
||
"/organizations",
|
||
axum::routing::get(org_handler::list_organizations)
|
||
.post(org_handler::create_organization),
|
||
)
|
||
.route(
|
||
"/organizations/{id}",
|
||
axum::routing::put(org_handler::update_organization)
|
||
.delete(org_handler::delete_organization),
|
||
)
|
||
// Department routes (nested under organization)
|
||
.route(
|
||
"/organizations/{org_id}/departments",
|
||
axum::routing::get(org_handler::list_departments)
|
||
.post(org_handler::create_department),
|
||
)
|
||
.route(
|
||
"/departments/{id}",
|
||
axum::routing::put(org_handler::update_department)
|
||
.delete(org_handler::delete_department),
|
||
)
|
||
// Position routes (nested under department)
|
||
.route(
|
||
"/departments/{dept_id}/positions",
|
||
axum::routing::get(org_handler::list_positions).post(org_handler::create_position),
|
||
)
|
||
.route(
|
||
"/positions/{id}",
|
||
axum::routing::put(org_handler::update_position)
|
||
.delete(org_handler::delete_position),
|
||
)
|
||
}
|
||
}
|
||
|
||
impl Default for AuthModule {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[async_trait::async_trait]
|
||
impl ErpModule for AuthModule {
|
||
fn name(&self) -> &str {
|
||
"auth"
|
||
}
|
||
|
||
fn version(&self) -> &str {
|
||
env!("CARGO_PKG_VERSION")
|
||
}
|
||
|
||
fn dependencies(&self) -> Vec<&str> {
|
||
// Auth is a foundational module with no business-module dependencies.
|
||
vec![]
|
||
}
|
||
|
||
fn register_event_handlers(&self, _bus: &EventBus) {
|
||
// Auth 模块暂无跨模块事件订阅需求
|
||
}
|
||
|
||
async fn on_tenant_created(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
db: &sea_orm::DatabaseConnection,
|
||
_event_bus: &EventBus,
|
||
) -> AppResult<()> {
|
||
let password = std::env::var("ERP__SUPER_ADMIN_PASSWORD").map_err(|_| {
|
||
tracing::error!("环境变量 ERP__SUPER_ADMIN_PASSWORD 未设置,无法初始化租户认证");
|
||
erp_core::error::AppError::Internal("ERP__SUPER_ADMIN_PASSWORD 未设置".to_string())
|
||
})?;
|
||
crate::service::seed::seed_tenant_auth(db, tenant_id, &password)
|
||
.await
|
||
.map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?;
|
||
tracing::info!(tenant_id = %tenant_id, "Tenant auth initialized");
|
||
Ok(())
|
||
}
|
||
|
||
async fn on_tenant_deleted(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
db: &sea_orm::DatabaseConnection,
|
||
) -> AppResult<()> {
|
||
use chrono::Utc;
|
||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
||
|
||
let now = Utc::now();
|
||
|
||
// 软删除该租户下所有用户
|
||
let users = crate::entity::user::Entity::find()
|
||
.filter(crate::entity::user::Column::TenantId.eq(tenant_id))
|
||
.filter(crate::entity::user::Column::DeletedAt.is_null())
|
||
.all(db)
|
||
.await
|
||
.map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?;
|
||
|
||
for user_model in users {
|
||
let current_version = user_model.version;
|
||
let active: crate::entity::user::ActiveModel = user_model.into();
|
||
let mut to_update: crate::entity::user::ActiveModel = active;
|
||
to_update.deleted_at = Set(Some(now));
|
||
to_update.updated_at = Set(now);
|
||
to_update.version = Set(current_version + 1);
|
||
let _ = to_update
|
||
.update(db)
|
||
.await
|
||
.map_err(|e| erp_core::error::AppError::Internal(e.to_string()))?;
|
||
}
|
||
|
||
tracing::info!(tenant_id = %tenant_id, "Tenant users soft-deleted");
|
||
Ok(())
|
||
}
|
||
|
||
fn permissions(&self) -> Vec<PermissionDescriptor> {
|
||
vec![
|
||
PermissionDescriptor {
|
||
code: "user.list".into(),
|
||
name: "查看用户列表".into(),
|
||
description: "查看用户列表".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "user.create".into(),
|
||
name: "创建用户".into(),
|
||
description: "创建新用户".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "user.read".into(),
|
||
name: "查看用户详情".into(),
|
||
description: "查看用户信息".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "user.update".into(),
|
||
name: "编辑用户".into(),
|
||
description: "编辑用户信息".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "user.delete".into(),
|
||
name: "删除用户".into(),
|
||
description: "软删除用户".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "user.reset-password".into(),
|
||
name: "重置用户密码".into(),
|
||
description: "管理员重置指定用户密码".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "role.list".into(),
|
||
name: "查看角色列表".into(),
|
||
description: "查看角色列表".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "role.create".into(),
|
||
name: "创建角色".into(),
|
||
description: "创建新角色".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "role.read".into(),
|
||
name: "查看角色详情".into(),
|
||
description: "查看角色信息".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "role.update".into(),
|
||
name: "编辑角色".into(),
|
||
description: "编辑角色".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "role.delete".into(),
|
||
name: "删除角色".into(),
|
||
description: "删除角色".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "permission.list".into(),
|
||
name: "查看权限".into(),
|
||
description: "查看权限列表".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "organization.list".into(),
|
||
name: "查看组织列表".into(),
|
||
description: "查看组织列表".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "organization.create".into(),
|
||
name: "创建组织".into(),
|
||
description: "创建组织".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "organization.update".into(),
|
||
name: "编辑组织".into(),
|
||
description: "编辑组织".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "organization.delete".into(),
|
||
name: "删除组织".into(),
|
||
description: "删除组织".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "department.list".into(),
|
||
name: "查看部门列表".into(),
|
||
description: "查看部门列表".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "department.create".into(),
|
||
name: "创建部门".into(),
|
||
description: "创建部门".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "department.update".into(),
|
||
name: "编辑部门".into(),
|
||
description: "编辑部门".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "department.delete".into(),
|
||
name: "删除部门".into(),
|
||
description: "删除部门".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "position.list".into(),
|
||
name: "查看岗位列表".into(),
|
||
description: "查看岗位列表".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "position.create".into(),
|
||
name: "创建岗位".into(),
|
||
description: "创建岗位".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "position.update".into(),
|
||
name: "编辑岗位".into(),
|
||
description: "编辑岗位".into(),
|
||
module: "auth".into(),
|
||
},
|
||
PermissionDescriptor {
|
||
code: "position.delete".into(),
|
||
name: "删除岗位".into(),
|
||
description: "删除岗位".into(),
|
||
module: "auth".into(),
|
||
},
|
||
]
|
||
}
|
||
|
||
fn as_any(&self) -> &dyn std::any::Any {
|
||
self
|
||
}
|
||
}
|