docs: design auth audit log table
This commit is contained in:
@@ -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` 映射与落地约束。
|
||||
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user