docs: design auth audit log table

This commit is contained in:
2026-04-21 02:14:22 +08:00
parent 5dd7528280
commit 84842e9475
5 changed files with 347 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
## 文档列表
- [SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md)`M2` 第四张鉴权审计表 `auth_audit_log` 的事件范围、追加写规则、索引与对外 DTO 派生约束。
- [SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md)`M2` 第三张会话表 `refresh_session` 的 cookie/hash 边界、轮换与吊销语义、索引与迁移规则。
- [SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md)`M2` 第二张身份表 `auth_identity` 的 provider 范围、唯一约束、手机号/微信身份写入规则与迁移策略。
- [SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md)`M2` 第一张身份主表 `user_account` 的职责边界、字段、唯一约束、状态迁移、旧 `users` 映射与落地约束。

View File

@@ -0,0 +1,339 @@
# `auth_audit_log` 表设计
日期:`2026-04-21`
## 1. 文档目的
这份文档用于完成 `M2` 的第四条任务:`设计 auth_audit_log`
目标是把鉴权审计表固定成一张“只追加的事实表”,并明确:
1. 哪些鉴权动作必须写审计
2. 审计表与风控表、会话表、账号表的边界
3. 哪些字段应该入库,哪些字段应该在读取时派生
4. `/api/auth/audit-logs` 要依赖什么读模型
## 2. 当前基线
当前 Node 后端已经存在 `auth_audit_logs` 表,并有完整写入链路:
1. `authService.ts` 通过 `writeAuthAuditLog(...)` 统一写入
2. `GET /api/auth/audit-logs` 会按用户倒序返回最近 20 条
3. 前端拿到的是已经过展示加工的 DTO
- `title`
- `detail`
- `ipMasked`
- `userAgent`
- `createdAt`
当前 Node `auth_audit_logs` 表字段基线:
1. `id`
2. `user_id`
3. `event_type`
4. `detail`
5. `ip`
6. `user_agent`
7. `meta_json`
8. `created_at`
当前已使用的事件类型基线:
1. `password_login`
2. `phone_login`
3. `wechat_login`
4. `wechat_bind_phone`
5. `change_phone`
6. `captcha_required`
7. `logout`
8. `logout_all`
9. `revoke_session`
10. `risk_block_phone`
11. `risk_block_ip`
12. `risk_unblock_phone`
13. `risk_unblock_ip`
## 3. 表职责边界
### 3.1 `auth_audit_log` 负责
1. 记录“账号已经发生过什么安全相关动作”
2. 记录动作发生时的操作者账号
3. 记录动作发生时的 IP、UA 与必要扩展上下文
4. 作为 `/api/auth/audit-logs` 的唯一事实来源
### 3.2 它不负责
1. 风控封禁当前是否生效
2. refresh session 当前是否活跃
3. 短信验证码发送频控
4. 账号当前状态
5. 展示标题的最终本地化文案
### 3.3 与其他表的边界
1. `auth_risk_block` 负责“当前拦截状态”
2. `auth_audit_log` 负责“发生过封禁或解除动作的历史事实”
3. `refresh_session` 负责“当前设备会话”
4. `auth_audit_log` 负责“谁在什么时候移除了哪台设备”
## 4. 访问级别
`auth_audit_log` 固定为 `private table`
原因:
1. 含用户行为安全记录
2. 含原始 IP 与原始 UA
3. 含 provider 或设备相关元信息
对外读取固定通过:
1. Axum 鉴权后按当前 `user_id` 查询
2. 再由 view / service 转成脱敏 DTO
## 5. 字段设计
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `audit_log_id` | `String` | 是 | 主键,建议继续沿用 `audit_*` 前缀。 |
| `user_id` | `String` | 是 | 归属账号 ID外键指向 `user_account.user_id`。 |
| `event_type` | `String` | 是 | 事件类型枚举。 |
| `detail` | `String` | 是 | 当前事件的人类可读详情,继续保留中文。 |
| `ip` | `Option<String>` | 否 | 事件发生时采集到的原始 IP。 |
| `user_agent` | `Option<String>` | 否 | 事件发生时采集到的原始 UA。 |
| `meta_json` | `Option<String>` | 否 | 扩展上下文 JSON 字符串,例如 `sessionId``targetIp``expiresAt`。 |
| `created_at` | `String` | 是 | 事件发生时间UTC RFC3339。 |
补充约束:
1. 当前阶段不增加 `updated_at`,因为审计事件一旦写入就不允许回写。
2. 当前阶段不单独存 `title`,继续由 `event_type` 在读取时派生。
3. `detail` 继续保留中文事实描述,避免后续只剩事件码却缺少可读上下文。
## 6. 事件类型设计
当前阶段 `event_type` 固定只支持以下值:
1. `password_login`
2. `phone_login`
3. `wechat_login`
4. `wechat_bind_phone`
5. `change_phone`
6. `captcha_required`
7. `logout`
8. `logout_all`
9. `revoke_session`
10. `risk_block_phone`
11. `risk_block_ip`
12. `risk_unblock_phone`
13. `risk_unblock_ip`
当前不新增更细子类型,原因:
1. 现有前端与 Node 展示已经围绕这 13 类建立
2. M2 重点是兼容迁移,不是先重做一套新的安全事件 taxonomy
后续若要扩展,只允许追加,不重命名已有事件码。
## 7. 展示派生规则
### 7.1 `title` 不入库
`/api/auth/audit-logs` 返回的 `title` 固定按 `event_type` 派生。
当前派生规则与现有 Node 对齐:
1. `password_login` -> `账号密码登录`
2. `phone_login` -> `手机号登录`
3. `wechat_login` -> `微信登录`
4. `wechat_bind_phone` -> `绑定手机号`
5. `change_phone` -> `更换手机号`
6. `captcha_required` -> `需要图形验证码`
7. `logout` -> `退出当前设备`
8. `logout_all` -> `退出全部设备`
9. `revoke_session` -> `移除登录设备`
10. `risk_block_phone` -> `手机号临时保护`
11. `risk_block_ip` -> `网络临时保护`
12. `risk_unblock_phone` -> `解除手机号保护`
13. `risk_unblock_ip` -> `解除网络保护`
### 7.2 `ipMasked` 不入库
原始 IP 继续入库,但对外返回时必须脱敏:
1. IPv4 -> `a.b.*.*`
2. IPv6 -> 保留前两段,其余隐藏
## 8. 唯一约束与索引
### 8.1 必须具备的唯一约束
1. `audit_log_id` 主键唯一
### 8.2 必须具备的查询索引
1. `(user_id, created_at DESC)`
作用:支撑 `/api/auth/audit-logs`
2. `(user_id, event_type, created_at DESC)`
作用:后续按事件分类筛选或管理后台排查
3. `(created_at DESC)`
作用:后续归档与清理窗口扫描
## 9. 写入规则
### 9.1 登录相关
必须写入:
1. 密码登录成功 -> `password_login`
2. 手机号登录成功 -> `phone_login`
3. 微信登录成功 -> `wechat_login`
4. 微信绑定手机号成功 -> `wechat_bind_phone`
### 9.2 手机号变更相关
必须写入:
1. 更换手机号成功 -> `change_phone`
2. 图形验证码被触发或校验失败 -> `captcha_required`
说明:
1. `captcha_required` 当前不是“成功动作”,而是一个安全门槛触发事件。
2. 继续沿用现有 Node 语义,不再拆成 `captcha_challenge_issued``captcha_verify_failed` 两类。
### 9.3 会话相关
必须写入:
1. 当前设备退出 -> `logout`
2. 退出全部设备 -> `logout_all`
3. 移除指定远端设备 -> `revoke_session`
### 9.4 风控相关
必须写入:
1. 手机号被封禁 -> `risk_block_phone`
2. IP 被封禁 -> `risk_block_ip`
3. 手机号封禁被解除 -> `risk_unblock_phone`
4. IP 封禁被解除 -> `risk_unblock_ip`
## 10. `meta_json` 约定
`meta_json` 当前只放扩展上下文,不放主字段副本。
### 10.1 推荐写入内容
1. `revoke_session`
- `sessionId`
- `targetIp`
- `targetUserAgent`
2. `risk_block_phone` / `risk_block_ip`
- `scopeKey`
- `expiresAt`
3. `captcha_required`
- `scene`
- `phoneNumberMasked`(如需要)
### 10.2 禁止写入内容
1. 原始 refresh token
2. 密码明文
3. 短信验证码明文
4. 微信 access token
## 11. 读取规则
### 11.1 `/api/auth/audit-logs`
当前阶段固定返回:
1. 最近 20 条
2.`created_at DESC`
3. 只返回当前用户自己的审计记录
### 11.2 DTO 转换规则
对外 DTO `AuthAuditLogEntry` 固定为:
1. `id`
2. `eventType`
3. `title`
4. `detail`
5. `ipMasked`
6. `userAgent`
7. `createdAt`
其中:
1. `id <- audit_log_id`
2. `title <- event_type` 派生
3. `ipMasked <- ip` 脱敏后派生
## 12. 与当前 Node `auth_audit_logs` 的映射关系
| Node 列 | 新字段 | 迁移规则 |
| --- | --- | --- |
| `id` | `audit_log_id` | 原样迁移。 |
| `user_id` | `user_id` | 原样迁移。 |
| `event_type` | `event_type` | 原样迁移。 |
| `detail` | `detail` | 原样迁移。 |
| `ip` | `ip` | 原样迁移。 |
| `user_agent` | `user_agent` | 原样迁移。 |
| `meta_json` | `meta_json` | 原样迁移。 |
| `created_at` | `created_at` | 原样迁移。 |
## 13. reducer / service 落地约束
### 13.1 `module-auth` reducer 层
必须至少具备:
1. `append_auth_audit_log`
说明:
1. 审计表是典型追加型事实表,不需要复杂更新 reducer。
2. 后续若做归档清理,再单独新增 maintenance reducer 或离线清理任务。
### 13.2 Axum 应用层
固定负责:
1. 决定在哪个业务动作成功或触发门槛时写审计
2. 组织 `detail`
3. 组织 `meta_json`
4. 读取时把 `event_type` 转为 `title`
## 14. 不允许的设计漂移
后续实现时禁止出现以下情况:
1.`title` 直接入库,导致显示文案和事件真相耦合
2. 为了省事复用 `auth_audit_log` 做当前风险态判断
3. 为了省事复用 `auth_audit_log` 做短信频控统计
4. 在审计表中记录原始 refresh token、验证码或密码明文
5. 更新已有审计记录,而不是追加新事实
## 15. 本任务完成定义
当以下条件满足时,`设计 auth_audit_log` 视为完成:
1. 审计表的职责边界已和会话表、风控表切开。
2. 事件类型、字段、索引与读取 DTO 派生规则已明确。
3. `/api/auth/audit-logs` 所需的查询语义已经固定。
4. 后续可以直接据此编码 reducer、view 与 Axum 读取逻辑。
## 16. 依据文件
1. `server-node/src/auth/authService.ts`
2. `server-node/src/repositories/authAuditLogRepository.ts`
3. `server-node/src/routes/authRoutes.ts`
4. `server-node/src/db/migrations.ts`
5. `packages/shared/src/contracts/auth.ts`
6. `docs/prd/MY_TAB_SETTINGS_AND_SECURITY_PRD_2026-04-16.md`
7. `docs/technical/SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md`
8. `docs/technical/SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md`
9. `docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md`

View File

@@ -318,6 +318,10 @@ server-rs/
- [SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md)
`auth_audit_log` 的事件范围、追加写规则与 DTO 派生约束,见:
- [SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md)
### B. 运行时主状态表
- `runtime_snapshot`