Files
hms/crates/erp-server/src/handlers/crypto_admin.rs
iven 6d5a711d2c
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
fix: 修复测试发现的 7 个问题 + 全 workspace clippy 清零
功能修复:
1. 患者创建空名称验证:后端添加 name.trim().is_empty() 检查
2. 仪表盘统计容错:单个查询失败返回零值而非 500
3. FHIR 路由修复:从 /fhir 移到 /api/v1/fhir 保持一致
4. 冻结模块后端中间件:新增 frozen_module_middleware 拦截冻结路径
5. 积分端点权限码:health.health-data.list → health.points.list
6. 角色权限迁移:护士补充 devices.list,运营补充 points.list/manage
7. 测试结果文档:R01-R05 角色测试 + T00/T10 结果归档

Clippy 全 workspace 清零(14→0 errors):
- erp-core: 修复 empty doc line、collapsible if、redundant closure 等 9 处
- erp-health: 修复 too_many_arguments、unused var、unnecessary parens 等 58 处
- erp-ai: 修复 dead_code、unused import 等 11 处
- erp-plugin: 修复 too_many_arguments、wildcard pattern 等 11 处
- erp-server-migration: 修复 enum_variant_names 5 处
- erp-auth/config/workflow/message: 各 1-3 处

工程改进:
- lint-staged 配置迁移到 .lintstagedrc.js(函数式避免文件列表传给 clippy)
- cargo fmt 统一格式化
2026-05-07 23:43:14 +08:00

77 lines
3.0 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::Extension;
use axum::Json;
use axum::extract::{FromRef, Path, State};
use sea_orm::{ConnectionTrait, DatabaseBackend, Statement};
use serde_json::{Value, json};
use uuid::Uuid;
use erp_core::error::AppError;
use erp_core::rbac::require_permission;
use erp_core::types::{ApiResponse, TenantContext};
use crate::state::AppState;
/// POST /api/v1/admin/tenants/:id/rotate-key
/// 密钥轮换 — 生成新 DEK持久化到 tenant_crypto_keys使缓存失效
pub async fn rotate_tenant_key<S>(
State(state): State<AppState>,
Extension(ctx): Extension<TenantContext>,
Path(tenant_id): Path<Uuid>,
) -> Result<Json<ApiResponse<Value>>, AppError>
where
AppState: FromRef<S>,
S: Clone + Send + Sync + 'static,
{
require_permission(&ctx, "tenant.manage")?;
// 读取当前最大版本号
let max_version: Option<i32> = {
let row = state.db.query_one(Statement::from_sql_and_values(
DatabaseBackend::Postgres,
"SELECT COALESCE(MAX(key_version), 0) as v FROM tenant_crypto_keys WHERE tenant_id = $1 AND deleted_at IS NULL",
[tenant_id.into()],
)).await.map_err(|e| AppError::Internal(format!("查询密钥版本失败: {}", e)))?;
row.and_then(|r| r.try_get_by_index::<i32>(0).ok())
};
let current_version = max_version.unwrap_or(0);
let new_version = current_version + 1;
// 将旧版本标记为不活跃
state.db.execute(Statement::from_sql_and_values(
DatabaseBackend::Postgres,
"UPDATE tenant_crypto_keys SET is_active = false, updated_at = now() WHERE tenant_id = $1 AND is_active = true AND deleted_at IS NULL",
[tenant_id.into()],
)).await.map_err(|e| AppError::Internal(format!("停用旧 DEK 失败: {}", e)))?;
// 生成新 DEK 并用 KEK 加密
let kek = state.pii_crypto.kek();
let (_new_dek, encrypted_dek) = erp_core::crypto::DekManager::generate_new_dek(kek)
.map_err(|e| AppError::Internal(format!("生成新 DEK 失败: {}", e)))?;
// 持久化新 DEK
let new_id = Uuid::now_v7();
state.db.execute(Statement::from_sql_and_values(
DatabaseBackend::Postgres,
"INSERT INTO tenant_crypto_keys (id, tenant_id, encrypted_dek, key_version, is_active, created_at, updated_at, version) VALUES ($1, $2, $3, $4, true, now(), now(), 1)",
[new_id.into(), tenant_id.into(), encrypted_dek.into(), new_version.into()],
)).await.map_err(|e| AppError::Internal(format!("存储新 DEK 失败: {}", e)))?;
// 使 DEK 缓存失效
state.pii_crypto.invalidate_dek(tenant_id);
tracing::info!(
tenant_id = %tenant_id,
old_version = current_version,
new_version = new_version,
"密钥轮换完成(新 DEK 已持久化,缓存已清除)"
);
Ok(Json(ApiResponse::ok(json!({
"message": "密钥轮换已完成",
"tenant_id": tenant_id,
"old_version": current_version,
"new_version": new_version,
"note": "后台重加密任务需要单独触发,旧数据仍可用旧 DEK 解密"
}))))
}