feat(health): patient_service 集成 PiiCrypto — 电话/过敏史/病史加密
- HealthState.crypto: HealthCrypto → PiiCrypto (erp-core) - create_patient: 加密 phone/allergy/medical_history + HMAC 索引 - update_patient: 同上,同步加密 - model_to_resp_decrypted: 解密所有 Tier 1 字段 - model_to_resp (列表): Tier 1 字段返回 None - list_patients 搜索: 新增 phone hash 精确搜索 - article handler: 适配新 list_articles 签名 - article 迁移: 添加 category_id 列 - error.rs: From<String> for HealthError - 集成测试: HealthCrypto → PiiCrypto::dev_default()
This commit is contained in:
@@ -212,14 +212,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if config.health.aes_key == "__MUST_SET_VIA_ENV__" || config.health.hmac_key == "__MUST_SET_VIA_ENV__" {
|
||||
tracing::error!(
|
||||
"健康数据加密密钥为默认占位值,拒绝启动。请设置环境变量 ERP__HEALTH__AES_KEY 和 ERP__HEALTH__HMAC_KEY(64 字符 hex 编码)"
|
||||
// 注: health 密钥已被统一 KEK (ERP__CRYPTO__KEK) 替代,此处仅保留兼容性检查
|
||||
tracing::warn!(
|
||||
"ERP__HEALTH__AES_KEY/HMAC_KEY 未设置(已迁移到 ERP__CRYPTO__KEK 统一密钥体系)"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Err(e) = erp_health::HealthCrypto::from_keys(&config.health.aes_key, &config.health.hmac_key) {
|
||||
tracing::error!("健康数据加密密钥无效: {}。密钥必须为 64 字符 hex 编码(32 字节)", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Initialize tracing
|
||||
@@ -449,6 +445,21 @@ async fn main() -> anyhow::Result<()> {
|
||||
};
|
||||
|
||||
// Build shared state
|
||||
let pii_crypto = if config.crypto.kek == "__MUST_SET_VIA_ENV__" {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
tracing::warn!("⚠️ PII KEK 使用开发默认值,仅用于本地开发");
|
||||
erp_core::crypto::PiiCrypto::dev_default()
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
panic!("ERP__CRYPTO__KEK must be set in production. Use a 64-char hex string (32 bytes).");
|
||||
}
|
||||
} else {
|
||||
erp_core::crypto::PiiCrypto::from_kek_hex(&config.crypto.kek)
|
||||
.expect("PII KEK must be valid 64-char hex (32 bytes). Set ERP__CRYPTO__KEK")
|
||||
};
|
||||
|
||||
let state = AppState {
|
||||
db,
|
||||
config,
|
||||
@@ -462,6 +473,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.time_to_idle(std::time::Duration::from_secs(300))
|
||||
.build(),
|
||||
ai_state,
|
||||
pii_crypto,
|
||||
};
|
||||
|
||||
// --- Build the router ---
|
||||
|
||||
@@ -22,6 +22,8 @@ pub struct AppState {
|
||||
pub plugin_entity_cache: moka::sync::Cache<String, erp_plugin::state::EntityInfo>,
|
||||
/// AI 模块状态(启动时构建,避免每次请求重建)
|
||||
pub ai_state: erp_ai::AiState,
|
||||
/// PII 加密服务(KEK + DEK 管理)
|
||||
pub pii_crypto: erp_core::crypto::PiiCrypto,
|
||||
}
|
||||
|
||||
/// Allow handlers to extract `DatabaseConnection` directly from `State<AppState>`.
|
||||
@@ -104,16 +106,10 @@ impl FromRef<AppState> for erp_plugin::state::PluginState {
|
||||
/// Allow erp-health handlers to extract their required state.
|
||||
impl FromRef<AppState> for erp_health::HealthState {
|
||||
fn from_ref(state: &AppState) -> Self {
|
||||
let crypto = erp_health::HealthCrypto::from_keys(
|
||||
&state.config.health.aes_key,
|
||||
&state.config.health.hmac_key,
|
||||
)
|
||||
.expect("Health encryption keys must be valid 32-byte hex strings. Set ERP__HEALTH__AES_KEY and ERP__HEALTH__HMAC_KEY");
|
||||
|
||||
Self {
|
||||
db: state.db.clone(),
|
||||
event_bus: state.event_bus.clone(),
|
||||
crypto,
|
||||
crypto: state.pii_crypto.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user