文件
hyb-oa/.trellis/tasks/08-09-production-admin-break-glass-reset/prd.md
T
wangsli 4fbfee2ff2
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:18:13 +08:00

13 KiB
原始文件 Blame 文件历史

生产管理员 break-glass 密码重置

Goal

在生产邮件自助重置不可用、唯一活跃管理员现有仓库外密码已确认不匹配的前提下,仅使用仓库既有 reset-active-admin-password.js 恢复管理员认证能力。任务必须先证明执行脚本与已部署候选一致、目标管理员唯一,再执行一次带字面确认串的事务写入;随后完成脚本要求的首次强制改密并销毁旧 hash 有限恢复材料。

本任务只完成“临时密码 → 强制改密 → 最终密码可用”的认证恢复生命周期,不进入合同或其他业务页面,不执行 08-08-production-authenticated-readiness 所属的合同登录态验收。恢复完成后回到该任务继续只读验收。

Background and Confirmed Facts

  • 前序任务已证明生产运行产品候选为 0224ad8,65 条 migration 全部 applied,连续四次 rollbackWindow=OPEN / terminatedRows=0,生产基线健康。
  • 生产只有 1 个活跃管理员;其 email 可唯一解析,未锁定、登录失败计数为 0,但现有仓库外管理员密码与生产 hash 不匹配。登录 attempts 仍为 0。
  • 生产 EMAIL_ENABLED=false,公网 capabilities 为 emailDeliveryReady=false / email_disabled;自助忘记密码不可用,且管理员 password reset 未锁定、请求计数为 0。
  • server/src/scripts/reset-active-admin-password.js 自提交 9b12451 起存在,在已部署候选 0224ad8 与当前 HEAD 之间没有变化。执行前仍必须比较运行容器脚本与已审查候选脚本的 SHA-256,不能只依赖 Git 历史结论。
  • 脚本 --preflight、--verify、--verify-password-changed、--diagnose-wecom-binding 均为只读;写入只在 --apply ACTIVE_ADMIN_BREAK_GLASS 分支发生,缺少字面确认串会返回 explicit_confirmation_required。
  • applyReset 只选择唯一 active、未 archived、具有 admin/system_admin 角色的账号,并在事务内再次校验状态、角色和 token version,目标变化会 fail-closed。
  • 所有会话、密码重置码、邮件登录码更新均限定 employee_id=target.id;不会撤销其他员工会话。
  • 脚本使用应用同源 hashPassword,写入 must_change_password=true。首次成功登录必定进入 /change-password;只有完成该页面的密码写入后,--verify-password-changed 才能 PASS。

Scope

  1. 对已发布来源、唯一生产目标、运行脚本 SHA、唯一管理员目标和当前认证/企微绑定状态做只读门禁。
  2. 在受控现场以最小权限短期保存旧 password hash,生成符合现行密码策略的一次性临时密码;二者均不回显、不进入仓库、task、普通日志或对话。
  3. 仅一次执行 reset-active-admin-password.js --apply ACTIVE_ADMIN_BREAK_GLASS,密码只经 stdin 输入;人工读取结构化结果并立即运行 --verify。
  4. 将唯一管理员 email 与临时密码原子写入仓库外 live-smoke secret,使用真实浏览器仅登录并完成强制改密;随后将最终密码原子更新到同一 secret。
  5. 运行 --verify-password-changed,确认强制改密完成;销毁临时凭据和旧 hash 有限恢复材料,形成脱敏交接记录。

Requirements

R1 来源、目标与脚本门禁

  • 执行来源必须 clean、committed、published,并通过既有生产精确身份/read-only preflight;不得使用 unpublished override。
  • 生产运行/公网 build 仍须匹配产品候选 0224ad8;当前任务文档 HEAD 不作为产品 build 预期。
  • 运行容器内脚本 SHA-256 必须与 git show 0224ad8:server/src/scripts/reset-active-admin-password.js 一致;不一致立即 PAUSED,不得从当前工作区向生产复制脚本。
  • 本地先运行脚本单元测试;测试失败、脚本漂移、目标身份不确定、migration/status 异常均停止。

R2 脚本只读前置

  • 先运行 --preflight,要求 status=PASS、activeAdminMatches=1、exactTarget=true;不能用此前聊天或旧查询替代现场结果。
  • 在写入前运行 --diagnose-wecom-binding,保存其布尔/计数摘要。exactTarget=false、目标冲突或查询失败立即停止;普通绑定存在本身不是密码重置阻塞,不修改/解绑企微。
  • 另取生产认证状态只读快照:must-change、token version、活跃会话/验证码计数和窗口状态只记录布尔/计数;不输出账号、email、hash 或生产路径。
  • --preflight、企微诊断、脚本 SHA 与旧 hash 保存完成前,禁止生成写入请求或调用 --apply。

R3 临时凭据与有限恢复材料

  • 临时密码与最终密码分别由 CSPRNG 生成,满足至少 12 位、含大小写和数字且不含公司名/常见序列;两者不得相同,也不得复用旧 secret。
  • 临时密码先写入本机仓库外、0600 的受控临时 secret;apply 从 stdin 读取,不放 argv、环境变量、shell history 或日志。apply PASS 且 --verify PASS 后,才把正确 email/临时密码原子更新到正式 live-smoke secret。
  • 旧 hash 仅保存于生产受控现场的随机 0600 文件,目录 0700;本地只记录 recoveryMaterialPresent=true,不得记录路径或 hash。
  • 旧 hash 不是完整或部分状态回滚:写回也不能恢复被撤销会话、清理的锁定/计数、token version、失效验证码或审计。它唯一用途是新凭据异常时提供一个已知格式正确的 hash。
  • 旧 hash 最晚在 --verify-password-changed PASS 后立即销毁;若流程 24 小时内未完成,也必须销毁并记录 recoveryMaterialDestroyed=true,不得无限保留。

R4 唯一写入与失败即停

  • 唯一 break-glass 写入命令为 node src/scripts/reset-active-admin-password.js --apply ACTIVE_ADMIN_BREAK_GLASS,执行次数严格等于 1。
  • 必须接受并记录脚本的认证副作用:password hash 更新、must_change_password=true、三个认证失败/锁定域清零、password-reset request 状态清零、token version +1、仅目标账号活跃会话撤销、重置码/邮件码失效、写入 SYSTEM_ADMIN_BREAK_GLASS_PASSWORD_RESET 审计。
  • --apply 非 PASS、SSH/容器通道中断、输出缺失/不可解析或目标变化时立即停止;不得盲重试第二次。先只读运行 --verify 定性实际状态,再回来取得新决策。
  • 不手写 password hash UPDATE,不修改角色、启用/归档状态、email、手机号或其他账号。

R5 写后验证与强制改密

  • apply 后立即运行 --verify,要求:唯一目标、mustChangePassword=true、locksCleared、activeSessions=0、activePasswordResetCodes=0、activeEmailLoginCodes=0、auditPresent=true。
  • --verify PASS 后,使用一次性 Playwright 浏览器 runner、单浏览器、零 retry 完成一次密码登录。runner 从 stdin/受控文件描述符读取凭据,不把密码放入 CLI argv、环境变量或持久 test spec;登录只允许 /login → /change-password,必须观察 mustChangePassword 跳转,不得进入 /portal 后的业务页面。
  • 强制改密使用临时密码作为旧密码、独立生成的最终密码作为新密码;只提交一次。失败立即停止,不重复猜测。
  • 改密成功后运行 --verify-password-changed,要求 mustChangePassword=false、locksCleared、activeSessions=1、passwordChangeAuditPresent=true、breakGlassAuditPresent=true。
  • --diagnose-wecom-binding 可在写前和最终验证后各运行一次,只记录绑定/同步/近期登录计数;不得尝试企微绑定、解绑或真机登录。

R6 Secret、清理与任务交接

  • 最终密码经 --verify-password-changed PASS 后原子更新仓库外 live-smoke secret;文件必须为 0600,不得留下临时副本或 shell history。
  • 清理临时密码文件、浏览器 storage/cookie、旧 hash 恢复材料;只记录布尔结论,不记录实际位置或内容。
  • 本任务不执行合同详情、终止按钮、员工权限负向或其他业务验收。重置任务完成后,恢复 08-08-production-authenticated-readiness,使用已确认最终凭据继续其只读 AC。
  • 任一敏感值出现在 stdout、artifact、task 或对话即 FAIL;先移除/轮换暴露材料,再继续。

Acceptance Criteria

  • AC1:执行来源 clean/published,生产身份、65 migration、产品 SHA 和运行脚本 SHA 门禁全部 PASS;未复制或替换生产脚本。
  • AC2:--preflight 为 PASS/1/true,写前 --diagnose-wecom-binding 与认证状态快照完成;目标唯一且无绑定冲突后才进入写入。
  • AC3:旧 hash 以 0700/0600 受控短期材料保存,临时/最终密码由 CSPRNG 独立生成并满足策略;任何敏感值均未回显或进入仓库/task/chat。
  • AC4:--apply ACTIVE_ADMIN_BREAK_GLASS 恰好执行一次且返回结构化 PASS;未执行手写 SQL 写入,未修改角色、启用状态、email 或其他账号。
  • AC5:紧随 apply 的 --verify PASS,脚本声明的 must-change、锁、会话、验证码、token/audit 副作用与实际计数自洽。
  • AC6:浏览器仅完成一次管理员登录和一次强制改密提交,按预期进入 /change-password;到 /portal 后立即停止,业务 API 实际发送数为 0,失败自动 retry 为 0。
  • AC7:--verify-password-changed PASS,最终仓库外 secret 原子更新且 0600;临时凭据、浏览器状态和旧 hash 均已销毁。
  • AC8:生产账号写入仅限唯一管理员认证生命周期;其他员工会话/账号、合同、权限、schema 与业务数据写入计数为 0。
  • AC9:只读 reviewer 已复核目标唯一性、apply 次数、副作用、secret 清理和证据脱敏;其核对对象为任务脱敏执行记录、源码与单测,不是独立生产重跑。task/process/governance/sensitive/diff 门禁全部 PASS。
  • AC10:任务交接明确说明只恢复管理员认证,不代表合同登录态验收通过;08-08-production-authenticated-readiness 继续保持 PAUSED,直至其自身 AC 完成。

Out of Scope

  • 合同台账/详情/终止按钮、员工权限负向、archived/terminated 样本或其他产品验收。
  • 员工账号选择或密码恢复;邮件通道启用、SMTP 配置、生产重启、忘记密码验证码流程。
  • 手写 SQL/Prisma 写入、角色/状态/email/手机号修改、创建新管理员、企微绑定/解绑或真机验证。
  • 部署应用、修改产品代码、脚本、schema、migration、生产 .env 或 feature flag。
  • 把旧 hash 当作完整回滚,删除审计,或为恢复历史会话/计数执行额外写入。

Accepted Risk and Stop Conditions

  • 生产只有一个活跃管理员。脚本写入后到强制改密完成前,管理员访问依赖临时密码;任何异常都必须停止,不能重复 apply。
  • apply 与 change-password 都会递增 token version、撤销会话和失效认证挑战;这是设计内副作用,不得用旧 hash 假装恢复这些状态。
  • 企微身份绑定记录不由脚本修改,但旧会话失效可能改变现有访问体验;只读诊断用于交接,不授权绑定写入。
  • 若 --verify BLOCKED、临时密码丢失或一次登录/改密失败,保留现场和有限恢复材料,在 24 小时上限内回来取得明确决策;禁止自行写回旧 hash。
  • 合同终止回滚窗口可能被真实用户并发关闭;本任务只读记录,不通过停用功能或配置变更阻止使用。

Open Questions

  • 无。强制改密被归类为重置生命周期,不是合同验收;合同与员工负向验收归还原 readiness task。

Test Plan

规划/本地

  • cd server && npm test -- tests/unit/scripts/reset-active-admin-password.test.js
  • node --check server/src/scripts/reset-active-admin-password.js
  • python3 ./.trellis/scripts/task.py validate 08-09-production-admin-break-glass-reset
  • node scripts/qa/check-process-contract.mjs --baseline --json
  • node scripts/qa/check-trellis-governance.mjs --json
  • node scripts/qa/check-sensitive-content.mjs
  • git diff --check

获批生产窗口

  • clean/published provenance、生产 read-only preflight、产品/脚本 SHA。
  • --preflight、写前 --diagnose-wecom-binding、有限恢复材料存在性。
  • 单次 --apply ACTIVE_ADMIN_BREAK_GLASS,随后 --verify。
  • 仓库外 secret 更新;一次性 Playwright runner 通过 stdin 单次登录/强制改密,不把凭据放入 argv/env。
  • --verify-password-changed、写后企微诊断、窗口复查、secret/hash/browser 清理。

Evidence Rules

  • 允许记录:commit/脚本 SHA 是否匹配、PASS/BLOCKED、目标计数、布尔、副作用计数、apply/login/change-password 次数和清理布尔。
  • 禁止记录:管理员 identifier/email、临时/最终密码、旧/新 hash、cookie/token、生产路径、连接串、数据库/SSH 身份、原始 payload。
  • 原始受控输出若包含敏感值不得进入 task;报告只保存稳定字段和相对 evidence 引用。