## 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 无。