fix: P0/P1 安全与质量缺陷修复 — 10 项 QA 审查问题解决
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled

P0 安全修复:
- tenant_rls: SQL 拼接改为参数化查询防止注入
- follow_up_service: UUID SQL 拼接改为参数化原生查询
- RLS 策略: 新迁移移除空字符串绕过条件
- SSE 消息推送: token 键名 'token' → 'access_token' 修复
- rate_limit: 登录端点 Redis 不可达时 fail-close

P1 质量修复:
- 小程序缓存清理: preservedKeys 补全认证键名
- 小程序 token 刷新: 失败时清除所有认证数据
- 小程序 401: redirectTo → reLaunch 兼容 tabBar
- 集成测试: 信号量限制并行数据库创建(4个)
- change_password: 乐观锁 version 硬编码 → 动态递增

测试: 516 全部通过 (含 153 集成测试)
This commit is contained in:
iven
2026-04-28 00:57:41 +08:00
parent 3d34e021a9
commit 9dd6095e77
11 changed files with 391 additions and 21 deletions

View File

@@ -180,10 +180,13 @@ pub async fn account_lockout_middleware(
) -> Response {
let avail = redis_avail();
// Redis 不可达时 fail-open放行请求
// Redis 不可达时 fail-close拒绝登录请求安全优先
if !avail.should_try().await {
tracing::warn!("Redis 不可达fail-open 账户锁定检查放行");
return next.run(req).await;
tracing::error!("Redis 不可达fail-close 拒绝登录请求");
return (StatusCode::SERVICE_UNAVAILABLE, axum::Json(RateLimitResponse {
error: "service_unavailable".to_string(),
message: "安全服务暂不可用,请稍后重试".to_string(),
})).into_response();
}
// 获取 Redis 连接
@@ -193,9 +196,12 @@ pub async fn account_lockout_middleware(
c
}
Err(e) => {
tracing::warn!(error = %e, "Redis 连接失败fail-open 账户锁定检查放行");
tracing::error!(error = %e, "Redis 连接失败fail-close 拒绝登录请求");
avail.mark_failed().await;
return next.run(req).await;
return (StatusCode::SERVICE_UNAVAILABLE, axum::Json(RateLimitResponse {
error: "service_unavailable".to_string(),
message: "安全服务暂不可用,请稍后重试".to_string(),
})).into_response();
}
};

View File

@@ -3,7 +3,7 @@ use axum::http::Request;
use axum::middleware::Next;
use axum::response::Response;
use erp_core::types::TenantContext;
use sea_orm::ConnectionTrait;
use sea_orm::{ConnectionTrait, DatabaseBackend, Statement};
/// Tenant RLS 中间件。
///
@@ -21,11 +21,12 @@ pub async fn tenant_rls_middleware(
let tenant_id = req.extensions().get::<TenantContext>().map(|ctx| ctx.tenant_id);
if let Some(tid) = tenant_id {
// SET app.current_tenant_id — RLS 策略读取此值
// SET app.current_tenant_id — RLS 策略读取此值(参数化查询防止注入)
if let Err(e) = db
.execute_unprepared(&format!(
"SET app.current_tenant_id = '{}'",
tid
.execute(Statement::from_sql_and_values(
DatabaseBackend::Postgres,
"SET app.current_tenant_id = $1",
[tid.into()],
))
.await
{