feat: add multi-device session identity

This commit is contained in:
2026-04-21 16:25:45 +08:00
parent a83c64133d
commit fcaf7bdb38
13 changed files with 1445 additions and 45 deletions

View File

@@ -13,6 +13,11 @@
3. `refresh_session``user_account.token_version` 的职责切分
4. 会话列表、当前设备识别、轮换与吊销的数据结构
补充约束:
1. 多端登录识别字段以 [MULTI_DEVICE_SESSION_IDENTITY_DESIGN_2026-04-21.md](./MULTI_DEVICE_SESSION_IDENTITY_DESIGN_2026-04-21.md) 为准。
2. 本文负责把这些字段正式并入 `refresh_session` 结构、迁移规则与接口读模型。
## 2. 当前基线
当前 Node 后端已经存在一张 `user_sessions` 表,并且 refresh cookie 主链已经完整可用:
@@ -44,6 +49,7 @@
1. refresh session 已经是现有系统的既有真相源。
2. Rust 重写时不需要重新发明另一套“session cache + cookie state”双轨模型。
3. 只需要把当前语义更明确地迁入 SpacetimeDB并把与 `user_account` 的职责切开。
4. 旧 Node 基线只有最小 `client_type + user_agent + ip` 粒度,本轮需要升级为结构化客户端身份模型。
## 3. 边界定义
@@ -129,7 +135,14 @@
| `session_id` | `String` | 是 | 主键,建议继续沿用 `usess_*` 前缀。 |
| `user_id` | `String` | 是 | 归属账号 ID外键指向 `user_account.user_id`。 |
| `refresh_token_hash` | `String` | 是 | 当前生效 refresh token 的哈希值。 |
| `client_type` | `String` | 是 | 当前设备类型,当前阶段默认 `browser`。 |
| `client_type` | `String` | 是 | 终端大类,固定枚举见多端会话身份设计。 |
| `client_runtime` | `String` | 是 | 具体运行时,例如 `chrome``wechat_mini_program`。 |
| `client_platform` | `String` | 是 | 平台类型,例如 `windows``ios``android`。 |
| `client_instance_id` | `Option<String>` | 否 | 客户端实例 ID用于区分同设备不同浏览器或同浏览器不同安装实例。 |
| `device_fingerprint` | `Option<String>` | 否 | 服务端派生的设备聚类指纹,不作为安全凭据。 |
| `device_display_name` | `String` | 是 | 用于会话列表展示的统一端侧名称。 |
| `mini_program_app_id` | `Option<String>` | 否 | 小程序 appid。 |
| `mini_program_env` | `Option<String>` | 否 | 小程序环境,例如 `develop``trial``release`。 |
| `user_agent` | `Option<String>` | 否 | 请求头中的 `User-Agent` 原文。 |
| `ip` | `Option<String>` | 否 | 会话创建时采集的客户端 IP。 |
| `issued_by_provider` | `String` | 是 | 该会话是由哪种登录链路创建,枚举固定为 `password``phone``wechat`。 |
@@ -145,6 +158,8 @@
1. 当前阶段时间字段统一继续使用 UTC RFC3339 字符串。
2. `session_id` 在 refresh 轮换时保持不变,不创建新会话行。
3. `issued_by_provider` 不是为了做 provider 身份表,而是为了后续账号安全页和审计展示保留稳定字段。
4. `device_display_name` 由 Axum 应用层基于结构化字段派生,不直接信任前端自由文本。
5. `client_type``client_runtime``client_platform` 的具体枚举口径固定受多端会话身份设计文档约束。
## 7. 唯一约束与索引
@@ -285,13 +300,21 @@
1. `session_id`
2. `client_type`
3. `user_agent`
4. `ip`
5. `created_at`
6. `last_seen_at`
7. `expires_at`
3. `client_runtime`
4. `client_platform`
5. `device_display_name`
6. `mini_program_app_id`
7. `mini_program_env`
8. `user_agent`
9. `ip`
10. `created_at`
11. `last_seen_at`
12. `expires_at`
前端 DTO `clientLabel``ipMasked``isCurrent` 继续在 Axum 侧派生。
前端 DTO 侧:
1. `clientLabel` 当前阶段继续兼容保留,但固定与 `deviceDisplayName` 对齐。
2. `ipMasked``isCurrent` 继续在 Axum 侧派生。
### 10.3 `POST /api/auth/logout`
@@ -314,7 +337,7 @@
| `id` | `session_id` | 原样迁移。 |
| `user_id` | `user_id` | 原样迁移。 |
| `refresh_token_hash` | `refresh_token_hash` | 原样迁移。 |
| `client_type` | `client_type` | 原样迁移。 |
| `client_type` | `client_type` | `browser` 在迁移后统一归一为 `web_browser`。 |
| `user_agent` | `user_agent` | 原样迁移。 |
| `ip` | `ip` | 原样迁移。 |
| `expires_at` | `expires_at` | 原样迁移。 |
@@ -330,6 +353,20 @@
说明:这是保守回填值,后续只影响展示,不影响鉴权正确性
2. `revoked_reason_code`
初次迁移统一回填为 `null`
3. `client_runtime`
初次迁移按 `User-Agent` 粗判,无法识别时回填 `unknown`
4. `client_platform`
初次迁移按 `User-Agent` 粗判,无法识别时回填 `unknown`
5. `client_instance_id`
初次迁移统一回填为 `null`
6. `device_fingerprint`
初次迁移按 `client_type + client_runtime + client_platform + normalized_user_agent` 派生
7. `device_display_name`
初次迁移由后端按多端会话身份规则派生
8. `mini_program_app_id`
初次迁移统一回填为 `null`
9. `mini_program_env`
初次迁移统一回填为 `null`
## 12. reducer / service 落地约束