liuyun 950e13a4a8
Some checks are pending
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run
docs(spec): 归档 force-password-change + 同步首登改密主 spec
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-11 21:57:51 +08:00

3.4 KiB
Raw Permalink Blame History

Context

用户已有 status(启停)、lockedAt/lockReason(锁定)与 tokenVersion;登录在 loginTenant 内校验,checkUserActive 每请求经 Redis user-state 缓存校验。PATCH /auth/me/password(changePassword)已支持改本人密码(校验原密码、tokenVersion+1、删 refresh)。动机见 proposal.md - Why。

Goals / Non-Goals

Goals:

  • 临时密码(管理员创建/重置、建租户随机密码)强制首登改密,且后端硬门禁不可绕过。

Non-Goals:

  • 密码复杂度/历史/过期策略(另立 change)。
  • 手机/邮箱找回密码流程。

Decisions

D1 — must_change_password 布尔列

User 新增 must_change_password(Boolean,非空,默认 false)。现有用户无需回填(默认 false,即不强制)。

D2 — 硬门禁内置于 JwtAuthGuard

user-state 缓存对象扩展为 { status, tokenVersion, lockedAt, mustChangePassword };checkUserActive 改为返回 { active, mustChangePassword }(单次 Redis 读)。守卫在 active 通过后,若 mustChangePassword 且请求非白名单 → 403「请先修改初始密码」。

  • 白名单仅 PATCH /auth/me/password(改本人密码)。login/refresh/logout 为 @Public 不经过守卫天然放行;登出依赖 refresh token、无需门禁。
  • 备选:独立 MustChangePasswordGuard(新 APP_GUARD)——否决,多一个全局守卫且需与 JwtAuthGuard 共享 user-state,不如内聚到一处。

D3 — 触发置标记

置 mustChangePassword=true:

  1. users.createUser:CreateUserDto 新增可选 mustChangePassword(默认 true,管理员可显式关闭);
  2. users.resetPassword(管理员重置,新临时密码,恒强制);
  3. tenants.createTenant(随机密码),经 initTenant 新增 forcePasswordChange 选项传入(恒强制)。

不置(false):

  • auth.register(用户自选密码);
  • seed 的平台自举管理员(touyun/Touyun@123,开发与 e2e/smoke 依赖其可直接登录)。

D4 — 登录返回标志、改密清除

loginTenant 返回 mustChangePassword(直接读已取到的 user 行),自动探测登录路径同步透传;changePassword 成功时 must_change_password: false。

D5 — 前端强制流程 + 状态持久化

auth store 新增 mustChangePassword,登录后写入并持久化(sessionStorage,抗刷新);路由守卫:待改密且目标非改密页/登出时重定向改密页。改密页为独立页(不进布局,不依赖 menus/permissions),提交成功后 reset() 清会话并跳登录。

  • 为什么独立页:硬门禁会 403 menus/permissions,若改密页在布局内会被门禁卡住。

Risks / Trade-offs

  • [门禁误伤] → 白名单最小化到仅 PATCH /auth/me/password;@Public 接口天然绕过不受影响。
  • [seed 管理员不强制] → 仅开发自举账号豁免;生产建租户/建用户/重置密码均强制,安全闭环不缺失。
  • [前端刷新丢状态] → 后端硬门禁兜底(即便前端标志丢失,其余接口仍 403);前端仍持久化标志以提供干净重定向体验。

Migration Plan

  1. Prisma migration:sys_user 加 must_change_password BOOLEAN NOT NULL DEFAULT false(无回填)。
  2. 代码上线(守卫门禁 + 登录标志 + 触发点)。
  3. 回滚:向后兼容,回滚代码即可。

Open Questions

无。