# `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` | 否 | 事件发生时采集到的原始 IP。 | | `user_agent` | `Option` | 否 | 事件发生时采集到的原始 UA。 | | `meta_json` | `Option` | 否 | 扩展上下文 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`