文件
hyb-oa/.trellis/tasks/archive/2026-08/08-09-production-admin-break-glass-reset/design.md
T
wangsli 0b298637dc
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
归档(08-09-production-admin-break-glass-reset): 收口管理员认证恢复
2026-08-09 09:19:00 +08:00

6.5 KiB
原始文件 Blame 文件历史

生产管理员 break-glass 密码重置:设计说明

1. 设计边界

本任务恢复唯一活跃管理员的认证能力,不修复产品、不启用邮件、不做合同验收。写入面由两个产品原生事务构成:break-glass 脚本写入临时密码,以及登录后的 /api/auth/change-password 写入最终密码。两者共同构成重置生命周期;除此之外没有生产账号或业务写入。

合同只读验收属于仍在 LOGIN PAUSED 的 08-08-production-authenticated-readiness,只有本任务完成、最终 secret 可用后才恢复。

2. 状态机与门禁

PLANNING
  -> source/target/runtime-script SHA(只读)
  -> --preflight PASS/1/true(只读)
  -> --diagnose-wecom-binding(只读)
  -> 旧 hash 有限恢复材料 + 临时/最终 secret 就绪
  -> --apply ACTIVE_ADMIN_BREAK_GLASS(唯一一次脚本写入)
  -> --verify PASS(只读)
  -> 正式 secret 写入临时凭据
  -> /login 一次 -> must-change redirect
  -> /change-password 一次(最终密码写入)
  -> --verify-password-changed PASS(只读)
  -> 正式 secret 写入最终凭据
  -> 写后诊断 / 清理旧 hash、临时 secret、浏览器状态
  -> RESET_COMPLETE / handoff readiness task

任何读门禁失败都停在写入前。apply 通道中断或非 PASS 时不得再 apply;先以 --verify 判断是否已经提交。一次登录/改密失败同样停止,不能消耗第二次尝试。

3. 被执行脚本的身份

本地当前脚本自已部署候选以来未变,但生产执行仍以三方事实确认:

  1. 公网/运行 build 为产品候选;
  2. git show <产品候选>:server/src/scripts/reset-active-admin-password.js 生成审查基线;
  3. 运行容器同路径 SHA-256 与基线精确一致。

只记录 scriptShaMatch=true。不把当前 HEAD 的任务文档提交当产品 build,也不向生产同步脚本。

4. 唯一目标与并发保护

--preflight 使用 findActiveAdmins(... take:2),只有 1 个 active、未 archived、具 admin/system_admin 的账号才 PASS。apply 前已取得目标 id/token version;事务内再读 status、archived、角色、token version。任何变化都抛 active_admin_target_changed,事务不写。

会话和 challenge 的 updateMany 均带 employee_id=target.id。审核时必须将返回计数理解为“唯一目标的副作用”,不能外推为全表操作。

5. 凭据生成、保管与流转

临时密码和最终密码由本机受控 Node/CSPRNG 进程生成,强制满足密码策略且不相同。值不进入环境变量或 argv:

  • 临时密码写到新建 0600 临时 secret,通过 stdin 喂给 apply;
  • 正确 email 只在受控进程中从唯一管理员记录取得;
  • apply/verify PASS 后,原子更新正式 live-smoke secret 为 email + 临时密码;
  • 强制改密成功后,再原子替换为最终密码;
  • 更新过程保留其他角色键,不生成明文备份副本。

若主 secret 更新失败,临时 secret 保留到恢复决策,避免 apply 已成功但密码丢失。所有清理只输出布尔。

6. 旧 hash 有限恢复材料

写前使用 read-only 查询读取唯一目标当前 hash,并由远端进程直接写入 0700 受控目录下随机 0600 文件。hash 不经过普通 stdout,不离开生产现场。

它不是完整或部分状态回滚。即使另行批准写回,也不能恢复 token version、会话、challenge、锁定计数或审计。默认禁止写回;仅当新凭据异常且获得新的明确写入批准时,才可把它当作一个格式已知的 hash 候选。

保留期限取以下较早者:

  • --verify-password-changed PASS;
  • apply 后 24 小时。

到期删除并验证 recoveryMaterialDestroyed=true。若 hash 未销毁,任务不得宣称完全收口。

7. 写入事务与验证

7.1 Break-glass apply

输入仅走 stdin,命令必须包含精确确认串。预期输出:唯一目标、passwordUpdated、mustChangePassword、tokenVersionIncremented、locksCleared、仅目标会话/challenge 计数和 auditCreated 均满足合同。

非零退出、缺失 JSON、status!=PASS 或 SSH 中断都进入 APPLY_UNCERTAIN。此时只允许 --verify;若 verify 显示写入已落地,禁止重试。

7.2 首次登录与强制改密

一次性 Playwright Node runner 使用新会话、单 browser、零 retry,并只从 stdin/受控文件描述符读取临时与最终密码;runner 源码不落仓库或 test spec,凭据不进入 argv/env:

  1. /login 填正确 email 和临时密码,提交一次;
  2. 必须跳到 /change-password;任何 /portal 或业务路由出现都视为 contract mismatch;
  3. 填临时密码、最终密码、确认密码,提交一次;
  4. 允许应用成功跳到 /portal,但立即停止,不点击任何业务入口;
  5. 清除浏览器 storage/cookie 并关闭 runner。

随后运行 --verify-password-changed。该验证要求唯一目标、must-change=false、locks cleared、activeSessions=1,以及改密和 break-glass 两类审计都存在。

8. 企微绑定影响

--diagnose-wecom-binding 写前和最终验证后均只读运行。它记录绑定存在、目录同步、冲突、近期密码/企微登录计数,不读取或输出 external userid。脚本不修改绑定;如出现冲突或目标不唯一,停止并另行处理,不在本任务绑定/解绑。

9. 失败矩阵

阶段 失败 动作
provenance/target/SHA 不一致 停止,零写入
preflight 非 PASS/1/true 停止,不选择目标
recovery material 无法安全保存 停止,不 apply
apply 明确失败 status=BLOCKED 停止,不重试
apply 通道不确定 无完整结果 只跑 --verify 定性
verify BLOCKED 保留现场,禁止登录
secret 更新 失败 保留受控临时 secret,停止
login/change-password 首次失败 停止,零 retry,不 apply 第二次
verify-password-changed BLOCKED 保留现场,另行决策
hash 清理 失败 标记敏感清理未完成,不归档

10. Reviewer 与交接

本任务涉及唯一生产管理员、认证状态机和真实账号写入,执行后必须由只读 reviewer 核对:脚本身份、目标唯一、apply 次数、事务副作用、两次密码写入、secret/hash 清理和零业务写入。reviewer 不修改代码、账号或现场材料。

最终只交接“管理员最终凭据已在仓库外 secret 就绪”和稳定计数;不交接凭据值。合同功能是否可用仍由原 readiness task 独立判定。