Files
wecom_it_smart_desk/docs/02-产品需求/05-增量PRD-OTP首次绑定与重置.md
Simon 400ce3ddcb feat: OTP首次绑定 + 三端登录修复 + 管理端权限修复 (2026-07-08)
OTP首次绑定:
- 新增统一 OTP 路由 /auth/otp-* (otp.py + router.py)
- 坐席端 OTP 绑定面板 (OtpBindPanel.vue)
- 管理端 OTP 管理列表 (MfaManage.vue)
- agent_login 签发半认证 token 支持首次绑定流程

三端登录修复:
- 坐席/管理端去掉'返回扫码登录'按钮
- 管理端改为二维码始终可见+轮询扫码状态
- 员工端 /itdesk/ 改为 alias 直接服务 H5 (不再301重定向)
- docker-compose 添加 h5 volume 挂载

管理端权限修复:
- 扫码登录改用 get_user_roles() 替代写死 roles=['agent']
- get_user_roles() 增加 agents.role 回退
- 新增 GET /admin/roles/user-roles 端点
- 角色管理页加载用户角色分配数据

文档更新:
- OTP PRD + 系统设计文档
- 故障排查手册 v1.1 (新增6案例)
- nginx 生产基准配置
2026-07-08 21:54:57 +08:00

15 KiB
Raw Permalink Blame History

增量 PRD — OTP 首次绑定流程及管理后台清除功能

文档版本: v1.0(增量)
创建日期: 2026-07-06
产品经理: Alice
状态: 待评审
关联文档:

  • docs/02-产品需求/04-增量PRD-三端认证重构.md — 母 PRD(认证重构,AUTH-P1-3 预留"首次绑定OTP引导"
  • docs/02-产品需求/02-产品需求文档PRD-v1.2-20260704.md — 主 PRD
  • backend/app/api/otp.py — OTP 六个端点已实现
  • backend/app/api/agents.py:274mfa_enabled=False 时直通登录(无绑定引导)
  • frontend-agent/src/views/Login.vue — OTP 输入框已实现 v-if="requireOtp",无绑定流程

0. 文档目标

本增量 PRD 旨在为 坐席端 OTP 首次绑定引导管理后台 OTP 清除功能 提供完整的交互定义。解决当前三大缺口:

  1. 新坐席 mfa_enabled=False → 账密通过后直通工作台,无绑定引导途径
  2. 坐席 OTP 丢失(换手机 / Authenticator 误删)后无法自助重新绑定
  3. 管理端缺少「清除已绑定 OTP」管理入口(后端已有 /otp-admin-reset,前端未接入)

注意:后端六个 OTP 端点(bind / verify / unbind / status / admin-reset / admin-users)已在 backend/app/api/otp.py 实现完成,本 PRD 聚焦前端交互与流程设计


1. 项目信息

字段
项目名称 wecom_it_smart_desk
文档语言 中文
Programming Language Vite + Vue3 + ElementPlus(前端不变)
原始需求 三端认证重构(PRD-04)已确定 OTP 接口与路由,本增量补充首次绑定引导与管理员重置的前端交互

2. 产品定义

2.1 产品目标

  1. 坐席首次登录闭环:新坐席账密验证通过后自动引导绑定 OTP,绑定成功后才能进入工作台——堵死"无 OTP 直通"漏洞
  2. 丢失恢复路径:坐席 OTP 丢失后,通过管理后台申请重置 → 重新走首次绑定流程
  3. 管理员可见可控:管理员可在后台查看全量坐席 OTP 绑定状态,并一键清除指定坐席的绑定

2.2 用户故事

ID 角色 用户故事 优先级
US-OTP-1 新坐席 (new agent) 作为首次登录的 IT 坐席,我希望账密验证通过后系统自动弹出二维码引导我绑定 OTP,扫码+输入验证码后即可进入工作台 P0
US-OTP-2 已有 OTP 的坐席 (agent) 作为已绑定 OTP 的坐席,我登录时输入账密后直接弹出 OTP 输入框验证,不要展示首次绑定流程 P0
US-OTP-3 OTP 丢失的坐席 (agent) 作为手机丢失/Authenticator 误删的坐席,我希望先在管理后台申请重置 OTP,然后下次登录时重新走绑定流程,最后能自助解绑+重新绑定 P1
US-OTP-4 管理员 (admin) 作为管理员,我希望在管理后台查看所有坐席的 OTP 绑定状态,并能在坐席丢失 OTP 时一键清除其绑定,使其下次登录强制重新绑定 P0
US-OTP-5 坐席 (agent) 作为已绑定 OTP 的坐席,我希望在设置页面能自助解绑 OTP(需验证当前 OTP 码),解绑后下次登录直接进入首次绑定流程 P1

3. 需求池

3.1 P0 — 必须实现

ID 需求 说明 / 验收标准
OTP-P0-1 首次登录 OTP 绑定引导 坐席账密验证通过 → 后端返回 require_otp_bind: true(而非 require_otp: true)→ 前端弹出 OTP 绑定面板(二维码 + secret 明文 + 6 位验证码输入框 + 确认按钮)→ 调用 /api/auth/otp-bind 获取 secret/QR → 用户扫码后输入 6 位码 → 调用 /api/auth/otp-verify → 验证通过后 mfa_enabled=True → 签发完整 token → 进入工作台
OTP-P0-2 管理后台 OTP 绑定状态列表 管理后台新增「OTP 管理」页面,列表展示:坐席姓名、employee_id、OTP 绑定状态(已绑定/未绑定)、绑定时间、最后验证时间。调用 GET /api/auth/otp-admin-users
OTP-P0-3 管理后台清除 OTP 绑定 管理员可在 OTP 管理页面点击「清除绑定」按钮 → 二次确认弹窗 → 调用 POST /api/auth/otp-admin-reset/{employee_id} → 该坐席下次登录强制走首次绑定流程
OTP-P0-4 后端区分"需要OTP验证"和"需要OTP绑定"两种状态 POST /agents/loginmfa_enabled=False 时,不直接签发 token,而是返回 require_otp_bind: true;前端据此展示绑定面板。mfa_enabled=True 时保持现有 require_otp: true

3.2 P1 — 应该实现

ID 需求 说明 / 验收标准
OTP-P1-1 坐席端自助解绑+重新绑定 坐席在「个人设置」页面看到 OTP 绑定状态,可点击「解绑 OTP」→ 输入当前 OTP 验证码 → 调用 POST /api/auth/otp-unbind → 解绑成功后 mfa_enabled=False → 下次登录自动进入首次绑定流程(复用 OTP-P0-1 面板)
OTP-P1-2 重新绑定流程 坐席已解绑后,登录时走 OTP-P0-1 绑定引导(与首次登录完全相同)。也可在设置页面提供「重新绑定」入口,主动触发绑定流程
OTP-P1-3 绑定流程中的"跳过"选项 首次绑定向导中提供"暂不绑定,稍后设置"按钮 → 跳过绑定直接进入工作台 → 在导航栏/设置页显示警告徽章提醒完成绑定 → 限制跳过次数或有效期(如仅可跳过 1 次)

3.3 P2 — 锦上添花

ID 需求 说明 / 验收标准
OTP-P2-1 OTP 绑定操作日志 管理员可查看 OTP 绑定/解绑/清除操作记录(操作人、操作时间、操作类型、目标坐席),调用现有后端日志即可
OTP-P2-2 批量清除 OTP 管理员可多选坐席后批量清除 OTP 绑定
OTP-P2-3 绑定截止日强制提醒 跳过绑定的坐席在 N 天后强制弹出绑定面板,不允许继续跳过

4. UI 设计稿说明

原型图目录:docs/04-原型设计/prototypes-原型图/

4.1 坐席端:首次绑定引导面板(新增)

触发时机handleLogin 收到 require_otp_bind: true 后展示。

布局(在现有 agent-login-v1.html 登录卡片内,替换 OTP 输入区):

┌──────────────────────────────────────┐
│  🛠️ IT智能服务台                      │
│  坐席工作台                           │
├──────────────────────────────────────┤
│                                      │
│  🔐 首次登录 — 绑定 OTP 二次验证      │
│                                      │
│  ┌──────────────────────────────┐    │
│  │                              │    │
│  │     [二维码 QR Code PNG]      │    │
│  │      200×200 px              │    │
│  │                              │    │
│  └──────────────────────────────┘    │
│                                      │
│  请使用 Google Authenticator          │
│  或 Microsoft Authenticator 扫码      │
│                                      │
│  ── 或手动输入密钥 ──                  │
│  密钥:XXXX XXXX XXXX XXXX            │
│  [📋 复制]                            │
│                                      │
│  ┌────────────────────────────────┐  │
│  │ 输入 6 位验证码                  │  │
│  └────────────────────────────────┘  │
│                                      │
│  [ 验证并完成绑定 ]  ← 主 CTA         │
│  [ 暂不绑定,稍后设置 ]  ← 次要       │
│                                      │
└──────────────────────────────────────┘

4.2 坐席端:登录页 OTP 验证(已有,无需改动)

保持现有 agent-login-v1.html 的 OTP 输入框逻辑:

  • v-if="requireOtp" 渲染 6 位 OTP 输入框
  • 按键文案「验证 OTP」

4.3 管理端:OTP 管理页面(新增)

入口:「坐席管理」→ 新增 Tab「OTP 绑定状态」,或在左侧菜单新增「OTP 管理」。

列表字段

列名 数据来源
坐席姓名 GET /otp-admin-usersname
企微 ID employee_id
OTP 状态 mfa_enabled → 「已绑定」/「未绑定」标签
绑定时间 mfa_bound_at
最后验证 mfa_last_verified_at
操作 「清除绑定」按钮(仅 mfa_enabled=true 时可用)

清除确认弹窗

⚠️ 确认清除 OTP 绑定?
坐席「张三 (zhangsan)」的 OTP 二次验证将被清除。
该坐席下次登录时需重新绑定。

[取消]  [确认清除]

4.4 坐席端:个人设置页 OTP 管理(P1,新增)

在坐席端「设置」页新增「OTP 二次验证」面板:

  • 已绑定状态:显示「 已绑定|绑定时间:xxxx-xx-xx」,提供「解绑 OTP」按钮
  • 解绑操作:弹出输入框要求输入当前 6 位 OTP 验证码 → 确认后调用 /api/auth/otp-unbind
  • 未绑定状态:显示「⚠️ 未绑定 — [立即绑定]」按钮 → 弹出与首次登录相同的绑定面板

5. 交互流程

5.1 首次绑定流程(P0 核心流程)

坐席打开 /itagent/login
  → 选择"账号密码登录"
  → 输入账号、密码 → 点击登录
  → POST /agents/login {user_id, password}
  → 后端返回:{ require_otp_bind: true, user_id, name }
  → 前端展示 OTP 绑定面板(隐藏账密表单)
  → 前端调用 POST /api/auth/otp-bind → 获得 { secret, otpauth_url, qr_code_base64 }
  → 渲染二维码 + secret 明文
  → 坐席用 Authenticator 扫码(或手动输入 secret
  → Authenticator 生成 6 位 TOTP 码
  → 坐席在输入框中输入 6 位码 → 点击"验证并完成绑定"
  → 前端调用 POST /api/auth/otp-verify { otp_code }
  → 后端:校验通过 → mfa_enabled=True, mfa_bound_at=now → 返回 { verified: true }
  → 前端再次调用 POST /agents/login {user_id, password}(或后端在 verify 成功后直接签发 token)
  → 进入工作台

5.2 OTP 丢失恢复流程(P0 + P1

坐席发现手机丢失/Authenticator 误删
  → 联系管理员(企微/电话)
  → 管理员登录管理后台 → OTP 管理页面
  → 找到该坐席 → 点击"清除绑定" → 确认
  → POST /api/auth/otp-admin-reset/{employee_id}
  → 坐席下次登录时 mfa_enabled=False → 触发首次绑定流程(5.1)

5.3 坐席自助解绑+重新绑定流程(P1)

坐席正常登录进入工作台
  → 打开"设置" → OTP 二次验证
  → 点击"解绑 OTP"
  → 弹窗输入当前 6 位 OTP 验证码 → 确认
  → POST /api/auth/otp-unbind { otp_code }
  → 解绑成功 → mfa_enabled=False
  → 下次登录自动进入首次绑定流程(5.1)

6. 关键设计决策

6.1 后端响应区分 require_otp vs require_otp_bind

POST /agents/login 当前逻辑(agents.py:274):

if agent.mfa_enabled:
    if not body.otp_code:
        return {"require_otp": True, ...}   # 已有 OTP → 要求验证
    else:
        # 校验 OTP → 签发 token
# 当 mfa_enabled=False 时,直接 fall through 到签发 token  ← 问题所在

建议改为

if agent.mfa_enabled:
    if not body.otp_code:
        return {"require_otp": True, "message": "请输入OTP动态码", ...}
    else:
        # 校验 OTP → 签发 token
else:
    # 未绑定 OTP → 引导绑定
    return {"require_otp_bind": True, "message": "首次登录请先绑定OTP二次验证", "user_id": agent.user_id, "name": agent.name}

6.2 绑定面板是"替换登录表单"还是"新页面跳转"

建议:在登录卡片内替换(隐藏账密表单,显示绑定面板),保持用户在同一页面上下文内,避免跳转带来的 token 状态管理复杂度。绑定成功后直接跳转工作台。

6.3 OTP 绑定成功后是否需要重新调用 login

建议POST /api/auth/otp-verifymfa_enabled=False 的绑定场景下,验证成功后直接返回完整 token(类似于注册+登录合并),避免前端二次调用 login。后端 otp.py:verify_otp 中当 mfa_enabled 从 False 变为 True 时,除了写 Redis 标记,还应签发 JWT token 一并返回。


7. 待确认问题

# 问题 建议 影响
Q1 首次绑定能否"跳过" 建议允许跳过 1 次(P1),在导航栏持续提醒"请完成 OTP 绑定",3 天后强制弹出绑定面板。也可 P0 阶段先不允许跳过,简化首版逻辑。 用户体验 vs 安全刚性
Q2 扫码登录的坐席是否需要 OTP 三端认证重构决策3 要求「所有登录均需 OTP」。如果是,扫码登录也需要区分 require_otp vs require_otp_bind。但扫码登录本身已是企微身份强验证,再叠加 OTP 可能过度。建议与架构师确认 扫码登录流程复杂度
Q3 OTP verify 绑定场景是否直接返回 token? 建议是(见 §6.3),避免前端二次调用 login 的状态同步问题。需要修改 otp.py:verify_otp:绑定场景(首次)额外返回 token。 后端改动量小
Q4 管理后台的 OTP 清除操作是否需要审计日志? 建议 P2 补充,P0 阶段先记 logger。后端已有 logger.info(如 otp.py:327)。 合规性
Q5 绑定面板中 secret 是否明文展示? 建议默认展示(带复制按钮),用折叠/展开切换"手动输入密钥"区域。这是标准 TOTP 实践,Google/Microsoft Authenticator 都支持手动输入。 无安全风险(secret 仅在绑定过程中临时展示)

8. 与母 PRD 的衔接关系

母 PRD 条目 衔接
AUTH-P1-3「首次绑定 OTP 引导」 本增量 PRD 将其从 P1 升级为 P0,并给出完整交互定义
AUTH-P0-5 OTP 接口统一 复用已有 /api/auth/otp-bind / otp-verify / otp-unbind / otp-status
AUTH-P0-4 OTP 输入框渲染时机 补充:首次绑定时渲染的是绑定面板(二维码+输入框),而非仅 OTP 输入框
CTRT-P0-1 响应契约 所有 OTP 接口均遵循统一信封,前端直接消费 inner data

文档结束 — 本增量 PRD 作为三端认证重构的子增量,聚焦 OTP 生命周期完整体验。待评审确认待确认问题后,移交架构师进行前端方案设计。