fix: 全面 QA 审计修复 — 安全加固/代码质量/跨平台一致性/测试覆盖
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

Phase 0 安全热修复 (CRITICAL):
- 外部化微信 appid/secret 到 ERP__WECHAT__APPID/SECRET 环境变量
- 正确连接 HealthCrypto 到 ERP__HEALTH__AES_KEY/HMAC_KEY 环境变量
- 外部化小程序加密密钥到 TARO_APP_ENCRYPTION_KEY 环境变量
- 移除小程序 auth store 中的敏感信息 console.log

Phase 1 安全加固:
- 微信自动注册 display_name 添加 sanitize 防止 XSS
- 测试数据库凭据改为从 TEST_DB_URL 环境变量读取

Phase 2 代码质量:
- 提取 useThemeMode hook 消除 22 处重复暗色模式检测
- 提取共享健康常量到 constants/health.ts
- 拆分 patient_service.rs 脱敏函数到 masking.rs
- 移除未使用的 i18next/react-i18next 依赖
- 移除未使用的 api/errors.ts 和 erp-auth/anyhow 依赖

Phase 3 测试覆盖:
- 新增 5 个患者模块集成测试 (CRUD/租户隔离/验证/软删除)

Phase 4 跨平台一致性:
- 统一小程序 Patient.birthday → birth_date 匹配后端
- 统一小程序 Appointment.time_slot → start_time/end_time 匹配后端

Phase 5 架构:
- 微信登录添加多租户 TODO 注释
- 更新 wiki/infrastructure.md 环境变量文档
This commit is contained in:
iven
2026-04-25 10:00:49 +08:00
parent 07f4ba41ba
commit 945ccd64ba
56 changed files with 634 additions and 273 deletions

View File

@@ -10,6 +10,7 @@ pub struct AppConfig {
pub log: LogConfig,
pub cors: CorsConfig,
pub wechat: WechatConfig,
pub health: HealthConfig,
}
#[derive(Debug, Clone, Deserialize)]
@@ -60,6 +61,14 @@ pub struct WechatConfig {
pub secret: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct HealthConfig {
/// AES-256 密钥 (64 字符 hex 编码32 字节)
pub aes_key: String,
/// HMAC-SHA256 密钥 (64 字符 hex 编码32 字节)
pub hmac_key: String,
}
impl AppConfig {
pub fn load() -> anyhow::Result<Self> {
let config = config::Config::builder()

View File

@@ -205,6 +205,22 @@ async fn main() -> anyhow::Result<()> {
);
std::process::exit(1);
}
if config.wechat.appid == "__MUST_SET_VIA_ENV__" || config.wechat.secret == "__MUST_SET_VIA_ENV__" {
tracing::error!(
"微信凭据为默认占位值,拒绝启动。请设置环境变量 ERP__WECHAT__APPID 和 ERP__WECHAT__SECRET"
);
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_KEY64 字符 hex 编码)"
);
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
tracing_subscriber::fmt()

View File

@@ -102,10 +102,16 @@ 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: erp_health::HealthCrypto::dev_default(),
crypto,
}
}
}