feat(merge): 4 个 worktree 合入 main(扫码+MFA+高危+P0)

合入内容:
- worktree-A (auth_qrcode): 13 测试  — Phase 1.1 后端扫码登录
- worktree-B (mfa): 21 测试  — Phase 2.1 MFA TOTP + User 字段
- worktree-C (high_risk_guard): 28 测试  — Phase 1.3 高危守卫
- worktree-D (p0-fixes): 16 测试  — P0/P1 合规(WS 签名+UUID+access_log)

合并方式: 各 worktree 提取 format-patch → 只 apply 新增文件 → 手动合并 router.py/dependencies.py 冲突

新文件 (16):
  backend/alembic/versions/022_qrcode_login.py
  backend/alembic/versions/023_mfa_fields.py
  backend/alembic/versions/025_messages_id_uuid.py
  backend/app/api/auth_qrcode.py
  backend/app/api/high_risk_routes.py
  backend/app/api/mfa.py
  backend/app/schemas/mfa.py
  backend/app/schemas/qrcode.py
  backend/app/services/high_risk_guard.py
  backend/app/services/mfa_service.py
  backend/app/services/qrcode_service.py
  backend/scripts/nginx-access-log-sanitize.sh
  backend/tests/test_auth_qrcode.py (13)
  backend/tests/test_high_risk_guard.py (28)
  backend/tests/test_mfa.py (21)
  backend/tests/test_messages_uuid.py
  backend/tests/test_ws_endpoints.py
  backend/tests/test_ws_push_to_employee.py (xfail 4)

修改 (4):
  backend/app/api/router.py — 注册 auth_qrcode/high_risk_routes/mfa 3 个 router
  backend/app/dependencies.py — 加 HIGH_RISK_OPERATIONS + require_high_risk_otp
  backend/app/models/agent.py — mfa_secret/mfa_enabled/mfa_bound_at/mfa_last_verified_at
  backend/tests/conftest.py — create_test_conversation 接 db_session

测试结果(新增 78 + xfail 4):
  tests/test_auth_qrcode.py      13 passed
  tests/test_high_risk_guard.py  28 passed
  tests/test_mfa.py              21 passed
  tests/test_messages_uuid.py     8 passed
  tests/test_ws_endpoints.py      8 passed
  tests/test_ws_push_to_employee.py 4 xfailed (端点路径不一致,pre-existing)

4 端 frontend build 全部通过(agent/portal/admin/h5)

后续 TODO (用户操作):
1. 撤销 Gitea token 5ad83d... via Web UI
2. 跑 alembic upgrade head(生产 PG,025 messages UUID)
3. 应用 nginx access_log 脱敏(进容器改 conf)
4. 部署 backend + 4 端 dist + nginx reload

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Simon
2026-06-21 03:08:54 +08:00
parent f564d0e42a
commit bf872da8bb
22 changed files with 4704 additions and 27 deletions
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# =============================================================================
# nginx access_log 脱敏脚本 — 不再记录 Authorization/Cookie 等敏感字段
# =============================================================================
# 背景(2026-06-21 评审):
# 当前 nginx 默认 access_log 格式包含 $http_authorization, $http_cookie,
# 这些字段含用户 token、session cookie,直接落盘到 /var/log/nginx/access.log。
# 任何能读该日志的运维都能冒充任意用户(严重安全漏洞)。
#
# 修复方案(对应 P1 合规):
# 1. 自定义 log_format "secure" — 不含 Authorization/Cookie/Set-Cookie
# 2. access_log 引用 "secure" 格式
# 3. 部署步骤: 在 nginx.conf http{} 块中插入下面的 log_format,
# 然后把 access_log 行的格式从默认改成 "secure"。
#
# 用法:
# 1. 在堡垒机上编辑 nginx.conf (宿主机路径或 docker exec 进容器改):
# docker exec -it wecom_it_nginx vi /etc/nginx/nginx.conf
# 2. 把本脚本输出的 "SECURE LOG_FORMAT 块" 插入到 http {} 块顶部
# 3. 把所有 access_log 行的格式参数从默认改成 "secure",例如:
# access_log /var/log/nginx/access.log secure;
# 4. nginx -t && nginx -s reload
# 5. 验证: curl -I https://... 看新日志是否含 "Bearer xxx"(不应该)
#
# ⚠️ 重要: 不要直接覆盖容器内 nginx.conf! bind mount RO 的话 docker cp 是假成功
# 陷阱回顾: backend/.claude/memory/feedback/docker-cp-readonly-bind-mount-fake-success.md
# =============================================================================
set -euo pipefail
# 输出需要插入到 nginx.conf http {} 块的 log_format 定义
cat <<'NGINX_SNIPPET'
# ----------------------------------------------------------------------------
# SECURE LOG_FORMAT — P1 合规: 不记录 Authorization/Cookie/Set-Cookie
# ----------------------------------------------------------------------------
# 与默认 combined 格式对比,删除了:
# $http_authorization — Bearer token,直接可冒充
# $http_cookie — Session cookie,直接可劫持
# $sent_http_set_cookie — 服务端下发的 session
#
# 默认 combined 格式: '$remote_addr - $remote_user [$time_local] '
# '"$request" $status $body_bytes_sent '
# '"$http_referer" "$http_user_agent"'
# ----------------------------------------------------------------------------
log_format secure '$remote_addr - $remote_user [$time_local] '
'"$request_method $uri $server_protocol" $status '
'$body_bytes_sent "$http_referer" '
'"$http_user_agent"';
# 关键改动: access_log 第二参数 = log_format 名称(默认 combined → 改 secure)
# 注意: 错误日志 error_log 不变(不含敏感字段)
access_log /var/log/nginx/access.log secure;
NGINX_SNIPPET
echo ""
echo "=========================================="
echo "P1 合规修复 — 操作步骤"
echo "=========================================="
echo ""
echo "1. 进入 nginx 容器(避开 bind mount RO 陷阱):"
echo " docker exec -it wecom_it_nginx sh"
echo ""
echo "2. 备份现有 nginx.conf:"
echo " cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d)"
echo ""
echo "3. 在 http {} 块内顶部插入上面输出的 SECURE LOG_FORMAT 块"
echo " (log_format + access_log 两行)"
echo ""
echo "4. 删除或注释原 access_log /var/log/nginx/access.log; 行(避免冲突)"
echo ""
echo "5. 测试配置 + 热重载:"
echo " nginx -t"
echo " nginx -s reload"
echo ""
echo "6. 验证: 触发一次带 Authorization 头的请求,grep access.log 应找不到 token"
echo " curl -H 'Authorization: Bearer TEST_TOKEN_DO_NOT_LOG' https://.../api/.../health"
echo " tail -1 /var/log/nginx/access.log # 不应含 TEST_TOKEN"
echo ""
echo "=========================================="
echo "回滚:"
echo "=========================================="
echo " cp /etc/nginx/nginx.conf.bak.YYYYMMDD /etc/nginx/nginx.conf"
echo " nginx -s reload"