From 39a12500e39df8d1b1a5a6b2613c9acd98974945 Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 17 Apr 2026 17:42:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(security):=20Q2=20Chunk=201=20=E2=80=94=20?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E5=A4=96=E9=83=A8=E5=8C=96=E4=B8=8E=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E5=BC=BA=E5=88=B6=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - default.toml 敏感值改为占位符,强制通过环境变量注入 - 启动时拒绝默认 JWT 密钥和数据库 URL - 移除 super_admin_password 硬编码 fallback - 移除 From for AuthError 反向映射,5 处调用点改为显式 map_err - .gitignore 添加 .test_token 和测试产物 --- .gitignore | 10 ++++++ crates/erp-auth/src/error.rs | 33 ------------------- crates/erp-auth/src/module.rs | 7 +++- crates/erp-auth/src/service/dept_service.rs | 3 +- crates/erp-auth/src/service/org_service.rs | 3 +- .../erp-auth/src/service/position_service.rs | 3 +- crates/erp-auth/src/service/role_service.rs | 3 +- crates/erp-auth/src/service/user_service.rs | 3 +- crates/erp-server/config/default.toml | 6 ++-- crates/erp-server/src/main.rs | 14 ++++++++ 10 files changed, 43 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 90306d0..1c1e001 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,13 @@ Thumbs.db # Docker data docker/postgres_data/ docker/redis_data/ + +# Test artifacts +.test_token +*.heapsnapshot +perf-trace-*.json +docs/debug-*.png + +# Development env +.env.development +docker/docker-compose.override.yml diff --git a/crates/erp-auth/src/error.rs b/crates/erp-auth/src/error.rs index 9dc1b2f..b8d7dcd 100644 --- a/crates/erp-auth/src/error.rs +++ b/crates/erp-auth/src/error.rs @@ -43,15 +43,6 @@ impl From for AppError { } } -impl From for AuthError { - fn from(err: AppError) -> Self { - match err { - AppError::VersionMismatch => AuthError::VersionMismatch, - other => AuthError::Validation(other.to_string()), - } - } -} - pub type AuthResult = Result; #[cfg(test)] @@ -104,28 +95,4 @@ mod tests { } } - #[test] - fn auth_error_version_mismatch_roundtrip() { - // AuthError::VersionMismatch -> AppError::VersionMismatch -> AuthError::VersionMismatch - let app: AppError = AuthError::VersionMismatch.into(); - match app { - AppError::VersionMismatch => {} - other => panic!("Expected VersionMismatch, got {:?}", other), - } - // And back - let auth: AuthError = AppError::VersionMismatch.into(); - match auth { - AuthError::VersionMismatch => {} - other => panic!("Expected VersionMismatch, got {:?}", other), - } - } - - #[test] - fn app_error_other_maps_to_auth_validation() { - let auth: AuthError = AppError::NotFound("not found".to_string()).into(); - match auth { - AuthError::Validation(msg) => assert!(msg.contains("not found")), - other => panic!("Expected Validation, got {:?}", other), - } - } } diff --git a/crates/erp-auth/src/module.rs b/crates/erp-auth/src/module.rs index 2d18317..93a78aa 100644 --- a/crates/erp-auth/src/module.rs +++ b/crates/erp-auth/src/module.rs @@ -147,7 +147,12 @@ impl ErpModule for AuthModule { _event_bus: &EventBus, ) -> AppResult<()> { let password = std::env::var("ERP__SUPER_ADMIN_PASSWORD") - .unwrap_or_else(|_| "Admin@2026".to_string()); + .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()))?; diff --git a/crates/erp-auth/src/service/dept_service.rs b/crates/erp-auth/src/service/dept_service.rs index cb32cc3..cff352d 100644 --- a/crates/erp-auth/src/service/dept_service.rs +++ b/crates/erp-auth/src/service/dept_service.rs @@ -190,7 +190,8 @@ impl DeptService { } } - let next_ver = check_version(req.version, model.version)?; + let next_ver = check_version(req.version, model.version) + .map_err(|e| AuthError::Validation(e.to_string()))?; let mut active: department::ActiveModel = model.into(); diff --git a/crates/erp-auth/src/service/org_service.rs b/crates/erp-auth/src/service/org_service.rs index 02257e8..cd983dc 100644 --- a/crates/erp-auth/src/service/org_service.rs +++ b/crates/erp-auth/src/service/org_service.rs @@ -173,7 +173,8 @@ impl OrgService { } } - let next_ver = check_version(req.version, model.version)?; + let next_ver = check_version(req.version, model.version) + .map_err(|e| AuthError::Validation(e.to_string()))?; let mut active: organization::ActiveModel = model.into(); diff --git a/crates/erp-auth/src/service/position_service.rs b/crates/erp-auth/src/service/position_service.rs index 5b565ca..bfb0a3a 100644 --- a/crates/erp-auth/src/service/position_service.rs +++ b/crates/erp-auth/src/service/position_service.rs @@ -165,7 +165,8 @@ impl PositionService { } } - let next_ver = check_version(req.version, model.version)?; + let next_ver = check_version(req.version, model.version) + .map_err(|e| AuthError::Validation(e.to_string()))?; let mut active: position::ActiveModel = model.into(); diff --git a/crates/erp-auth/src/service/role_service.rs b/crates/erp-auth/src/service/role_service.rs index 998a7b0..4bdaa83 100644 --- a/crates/erp-auth/src/service/role_service.rs +++ b/crates/erp-auth/src/service/role_service.rs @@ -171,7 +171,8 @@ impl RoleService { .filter(|r| r.tenant_id == tenant_id && r.deleted_at.is_none()) .ok_or_else(|| AuthError::Validation("角色不存在".to_string()))?; - let next_ver = check_version(version, model.version)?; + let next_ver = check_version(version, model.version) + .map_err(|e| AuthError::Validation(e.to_string()))?; let mut active: role::ActiveModel = model.into(); diff --git a/crates/erp-auth/src/service/user_service.rs b/crates/erp-auth/src/service/user_service.rs index 87aac34..630f505 100644 --- a/crates/erp-auth/src/service/user_service.rs +++ b/crates/erp-auth/src/service/user_service.rs @@ -200,7 +200,8 @@ impl UserService { .filter(|u| u.tenant_id == tenant_id && u.deleted_at.is_none()) .ok_or_else(|| AuthError::Validation("用户不存在".to_string()))?; - let next_ver = check_version(req.version, user_model.version)?; + let next_ver = check_version(req.version, user_model.version) + .map_err(|e| AuthError::Validation(e.to_string()))?; let mut active: user::ActiveModel = user_model.into(); diff --git a/crates/erp-server/config/default.toml b/crates/erp-server/config/default.toml index 001a9fc..78474cc 100644 --- a/crates/erp-server/config/default.toml +++ b/crates/erp-server/config/default.toml @@ -3,7 +3,7 @@ host = "0.0.0.0" port = 3000 [database] -url = "postgres://erp:erp_dev_2024@localhost:5432/erp" +url = "__MUST_SET_VIA_ENV__" max_connections = 20 min_connections = 5 @@ -11,12 +11,12 @@ min_connections = 5 url = "redis://localhost:6379" [jwt] -secret = "change-me-in-production" +secret = "__MUST_SET_VIA_ENV__" access_token_ttl = "15m" refresh_token_ttl = "7d" [auth] -super_admin_password = "Admin@2026" +super_admin_password = "__MUST_SET_VIA_ENV__" [log] level = "info" diff --git a/crates/erp-server/src/main.rs b/crates/erp-server/src/main.rs index 5e9e8f9..260cf9b 100644 --- a/crates/erp-server/src/main.rs +++ b/crates/erp-server/src/main.rs @@ -186,6 +186,20 @@ async fn main() -> anyhow::Result<()> { // Load config let config = AppConfig::load()?; + // ── 安全检查:拒绝默认密钥 ────────────────────────── + if config.jwt.secret == "__MUST_SET_VIA_ENV__" || config.jwt.secret == "change-me-in-production" { + tracing::error!( + "JWT 密钥为默认值,拒绝启动。请设置环境变量 ERP__JWT__SECRET" + ); + std::process::exit(1); + } + if config.database.url == "__MUST_SET_VIA_ENV__" { + tracing::error!( + "数据库 URL 为默认占位值,拒绝启动。请设置环境变量 ERP__DATABASE__URL" + ); + std::process::exit(1); + } + // Initialize tracing tracing_subscriber::fmt() .with_env_filter(