feat: add password entry auth flow

This commit is contained in:
2026-04-21 14:50:42 +08:00
parent 5675c40119
commit c23088539e
18 changed files with 1146 additions and 25 deletions

View File

@@ -0,0 +1,253 @@
# 密码登录与自动建号落地设计
日期:`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. 手机验证码登录
微信登录继续按“暂缓执行”处理,直到用户重新解锁。

View File

@@ -4,6 +4,7 @@
## 文档列表
- [PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md](./PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md):密码登录与自动建号落地设计,冻结 `/api/auth/entry`、幂等兼容策略、模块边界以及与 JWT / refresh cookie 的衔接方式。
- [PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md](./PLATFORM_AUTH_REFRESH_COOKIE_ADAPTER_DESIGN_2026-04-21.md)`platform-auth` refresh cookie 适配设计,冻结 cookie 配置结构、读取规则与 `api-server` 最小读取链路。
- [PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md](./PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md)`platform-auth` 首版 JWT 适配设计,冻结 `JwtConfig`、claims 结构、`HS256` 签发/校验、`api-server` Bearer 中间件与内部验收路由边界。
- [OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md](./OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md):面向 Axum、`platform-auth``SpacetimeDB` 身份透传的 OIDC 风格 JWT claims 设计,冻结 `iss/sub/sid/provider/roles` 等关键字段。