Files
Genarrative/docs/technical/SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

9.2 KiB
Raw Permalink Blame History

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 字符串,例如 sessionIdtargetIpexpiresAt
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_issuedcaptcha_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