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
+39 -1
View File
@@ -9,7 +9,7 @@ import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import DateTime, Integer, JSON, String
from sqlalchemy import Boolean, DateTime, Integer, JSON, String, text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
@@ -150,6 +150,44 @@ class Agent(Base):
comment="本地密码哈希(bcrypt",
)
# --------------------------------------------------------------------------
# MFA 二次认证字段(Phase 2.1 task #17
# --------------------------------------------------------------------------
# 说明:MFA TOTP 独立于早期 OTP 字段,采用全新字段名以便区分演进阶段。
# - mfa_secret: TOTP 共享密钥(base32),绑定时生成,首次验证前不算启用
# - mfa_enabled: 是否启用(仅当 bind/confirm 验证成功后置 true)
# - mfa_bound_at: 首次绑定完成时间(用于审计 + 回收策略)
# - mfa_last_verified_at: 最近一次 verify 成功时间(用于安全审计)
# --------------------------------------------------------------------------
mfa_secret: Mapped[Optional[str]] = mapped_column(
String(32),
nullable=True,
default=None,
comment="MFA TOTP 共享密钥(base32,绑定时生成)",
)
mfa_enabled: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default=text("false"),
comment="MFA 是否启用(False/True)",
)
mfa_bound_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
nullable=True,
default=None,
comment="MFA 首次绑定完成时间",
)
mfa_last_verified_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
nullable=True,
default=None,
comment="MFA 最近一次验证成功时间",
)
def __repr__(self) -> str:
"""坐席对象的字符串表示,方便调试。"""
return (