Files
Genarrative/docs/technical/PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md

256 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 密码登录历史落地设计
> 2026-04-24 更新:当前产品策略已调整为“不开放密码注册”。新用户必须通过手机号验证码注册/登录,密码登录只面向已经设置密码的账号。密码修改与重置以 [PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md](./PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md) 为准;本文中“密码自动建号”仅保留为历史基线说明,不再作为当前落地依据。
日期:`2026-04-21`
## 1. 文档目的
这份文档用于指导 `M2` 中以下两条任务的首版落地:
1. `实现密码登录`
2. `实现账号自动创建 / 幂等登录兼容策略`
目标是先把当前 Node 已经稳定运行的 `/api/auth/entry` 语义迁到 Rust 工作区,并冻结:
1. `api-server` 对外暴露的最小兼容接口。
2. `module-auth` 负责的密码登录用例边界。
3. 自动建号与并发幂等兼容规则。
4. 登录成功后与 JWT、refresh cookie 的衔接方式。
## 2. 当前基线
当前 Node `/api/auth/entry` 主链已经具备如下语义:
1. 输入 `username + password`
2. 若用户名不存在,则自动创建一个本地账号。
3. 若用户名已存在,则校验密码。
4. 登录成功后签发 access token。
5. 同时创建 refresh session并把原始 refresh token 写入 HttpOnly cookie。
6. 并发创建同一用户名时,后到的请求会回退为“查已存在账号并校验密码”,不因唯一键冲突直接失败。
这条链路既是当前前端匿名/游客恢复的基础,也是真实 `/api/auth/entry` contract 的既有事实,因此 Rust 首版必须兼容。
## 3. 设计输入
本任务直接受以下文档约束:
1. [SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md)
2. [SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md)
3. [OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md](./OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md)
4. [PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md](./PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md)
5. [PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md](./PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md)
关键冻结点:
1. `password_hash` 当前继续由 `user_account` 承担,不进入 `auth_identity`
2. `sub` 必须是稳定 `user_id`
3. 登录成功后必须继续同时生成 access token 和 refresh session。
4. 自动建号兼容必须保留,不能因为迁到 Rust 就删除。
## 4. 首版落地范围
本阶段只落以下内容:
1. `module-auth` 中的密码登录用例。
2. `api-server` 中的 `POST /api/auth/entry`
3. 用户名校验、密码哈希校验与自动建号。
4. 登录成功后的 access token 与 refresh cookie 主链打通。
本阶段明确不包含:
1. SpacetimeDB 真正的 `user_account` / `refresh_session` reducer 写入。
2. `/api/auth/me``/api/auth/logout``/api/auth/refresh` 的正式业务闭环。
3. 手机验证码与微信登录链路。
## 5. crate 边界
### 5.1 `module-auth`
负责:
1. 用户名与密码的领域校验。
2. 密码登录主用例。
3. 自动建号与并发幂等兼容策略。
4. 输出登录成功所需的最小用户快照。
不负责:
1. JWT 编解码。
2. refresh cookie 解析与写回。
3. HTTP 请求解析与响应拼装。
### 5.2 `platform-auth`
负责:
1. 密码哈希与校验适配。
2. JWT 签发与校验。
3. refresh cookie 读写适配。
不负责:
1. 决定账号是否应当自动创建。
2. 决定用户状态是否合法。
### 5.3 `api-server`
负责:
1. 解析 `POST /api/auth/entry` 请求体。
2. 调用 `module-auth` 用例。
3. 调用 `platform-auth` 签发 token 和 refresh cookie。
4. 返回与旧接口兼容的 JSON body。
## 6. 请求与响应 contract
### 6.1 请求体
固定沿用当前 contract
```json
{
"username": "guest_001",
"password": "secret123"
}
```
### 6.2 成功响应
固定沿用当前 contract
```json
{
"token": "<access-token>",
"user": {
"id": "user_xxx",
"username": "guest_001",
"displayName": "guest_001",
"phoneNumberMasked": null,
"loginMethod": "password",
"bindingStatus": "active",
"wechatBound": false
}
}
```
同时响应头必须写回 refresh cookie。
## 7. 用户名与密码规则
当前阶段继续对齐 Node 基线:
1. `username` 只允许 `3``24` 位字母、数字、下划线。
2. `password` 长度必须在 `6``128` 位之间。
任一校验失败时:
1. 返回 `400 BAD_REQUEST`
2. 错误文案继续保持中文
## 8. 自动建号与幂等兼容
### 8.1 自动建号
`username` 不存在时:
1. 用当前请求里的 `password` 生成密码哈希。
2. 创建一条本地账号。
3. `display_name = username`
4. `login_provider = password`
5. `account_status = active`
6. `token_version = 1`
### 8.2 已存在账号
`username` 已存在时:
1. 校验密码哈希。
2. 校验失败返回 `401 UNAUTHORIZED`
3. 校验成功继续登录。
### 8.3 并发幂等兼容
若两个请求并发创建同一用户名:
1. 允许其中一个请求先创建成功。
2. 后一个请求若命中唯一键冲突,不直接失败。
3. 后一个请求必须重新查询该用户名。
4. 若查到账号,则按“已存在账号”路径继续校验密码。
这保证了当前前端重复调用 `/api/auth/entry` 时可以恢复同一账号,而不是随机失败。
## 9. 首版存储策略
当前阶段为了先跑通工程闭环,固定采用:
1. `module-auth` 内的进程内内存仓储适配器作为临时真相。
说明:
1. 这是阶段性工程策略,不改变最终 `SpacetimeDB` 作为真相源的目标。
2. 当前这样做是为了先把 crate 边界、用例形状、HTTP contract、JWT / refresh cookie 主链稳定下来。
3. 后续切到 `SpacetimeDB` 时,应保持 `module-auth` 用例接口不变,只替换仓储实现。
## 10. 密码哈希策略
当前阶段继续对齐 Node
1. `Argon2id`
说明:
1. Rust 侧不再复用 Node 原生库,但哈希语义继续保持同类算法。
2. 当前目标是“工程能力闭环”,不是做跨语言哈希值兼容迁移。
3. 若未来需要与 Node 历史哈希共存,需单独补兼容文档和迁移策略。
## 11. 与 JWT / refresh cookie 的衔接
密码登录成功后:
1. `module-auth` 返回最小用户领域对象。
2. `api-server` 基于该对象构造 `AccessTokenClaimsInput`
3. `platform-auth` 签发 access token。
4. `platform-auth` 生成 refresh token 与 `Set-Cookie` 头。
5. `api-server` 返回 `token + user`
当前阶段固定 claims 值:
1. `provider = password`
2. `roles = ["user"]`
3. `binding_status = active`
4. `phone_verified = false`
5. `display_name = username`
## 12. 测试策略
当前阶段至少覆盖:
1. 首次密码登录自动建号成功。
2. 同用户名同密码可重复登录同一账号。
3. 同用户名不同密码返回 `401`
4. 非法用户名返回 `400`
5. 登录成功时返回 access token。
6. 登录成功时写回 refresh cookie。
## 13. 完成定义
满足以下条件时,本任务视为完成:
1. `module-auth` 不再只是 README占位被真实 crate 实现替换。
2. `POST /api/auth/entry` 可在 Rust 侧独立跑通。
3. 自动建号与幂等兼容行为可验证。
4. JWT 与 refresh cookie 登录成功主链打通。
5. 文档、任务清单与测试同步完成。
## 14. 后续衔接
这条任务完成后,下一步顺序固定为:
1. `me` 查询
2. refresh token 轮换
3. 会话吊销
4. 手机验证码登录
微信登录继续按“暂缓执行”处理,直到用户重新解锁。