Files
hms/crates/erp-auth/src/module.rs
iven f05ca00c75
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
feat(auth+config+workflow+message+plugin): 为 5 个基础模块添加 permissions() 声明
- erp-auth: 23 个权限码(用户/角色/权限/组织/部门/岗位)
- erp-config: 18 个权限码(字典/菜单/配置/编号/主题/语言)
- erp-workflow: 8 个权限码(流程定义/实例/任务)
- erp-message: 5 个权限码(消息/模板),补充缺失的 message.template.manage
- erp-plugin: 2 个权限码(插件管理/查看)
- 同步更新 seed.rs 的 READ_PERM_INDICES 索引和权限计数

使得 sync_module_permissions() 可以动态注册这些权限,与 erp-health/erp-dialysis/erp-ai 模式一致。
2026-04-30 22:41:26 +08:00

243 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/refresh", axum::routing::post(auth_handler::refresh))
.route(
"/auth/wechat/login",
axum::routing::post(wechat_handler::wechat_login),
)
.route(
"/auth/wechat/bind-phone",
axum::routing::post(wechat_handler::wechat_bind_phone),
)
}
/// 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(
"/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 sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use chrono::Utc;
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: "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
}
}