docs: design sms auth event table

This commit is contained in:
2026-04-21 10:08:11 +08:00
parent 90bc0edf9a
commit 5a60ab3972
5 changed files with 373 additions and 2 deletions

View File

@@ -4,6 +4,7 @@
## 文档列表
- [SPACETIMEDB_SMS_AUTH_EVENT_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_SMS_AUTH_EVENT_TABLE_DESIGN_2026-04-21.md)`M2` 第六张短信鉴权统计表 `sms_auth_event` 的事件范围、统计口径、索引与和风控/审计表的协作边界。
- [SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md)`M2` 第五张风控状态表 `auth_risk_block` 的作用域、活跃态、刷新/解除规则与读取派生约束。
- [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 边界、轮换与吊销语义、索引与迁移规则。

View File

@@ -326,6 +326,10 @@ server-rs/
- [SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md)
`sms_auth_event` 的事件范围、发送/校验写入规则、统计口径与和风控/审计表的边界,见:
- [SPACETIMEDB_SMS_AUTH_EVENT_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_SMS_AUTH_EVENT_TABLE_DESIGN_2026-04-21.md)
### B. 运行时主状态表
- `runtime_snapshot`

View File

@@ -0,0 +1,364 @@
# `sms_auth_event` 表设计
日期:`2026-04-21`
## 1. 文档目的
这份文档用于完成 `M2` 的第六条任务:`设计 sms_auth_event`
目标是把短信鉴权事件表固定成一张“短信发送与验证码校验的统计源表”,并明确:
1. 哪些短信相关动作要写入事件
2. 哪些动作不应该写进这张表
3. 发送频控、失败次数限制、captcha 触发、风险保护分别依赖什么统计口径
4. 它与 `auth_risk_block``auth_audit_log` 的边界
## 2. 当前基线
当前 Node 后端已经存在 `sms_auth_events` 表,并有稳定的读写链路:
1. `POST /api/auth/phone/send-code` 在短信发送成功后写入一条 `send_code` 事件
2. `POST /api/auth/phone/login` 在验证码校验成功或失败后写入 `verify_code` 事件
3. `POST /api/auth/wechat/bind-phone` 在绑定手机号时复用同一套 `verify_code` 事件
4. `POST /api/auth/phone/change` 在换绑手机号时复用同一套 `verify_code` 事件
5. 发送频控、失败次数限制、captcha 触发与风险保护,全部依赖这张表统计
当前 Node `sms_auth_events` 字段基线:
1. `id`
2. `phone_number`
3. `scene`
4. `action`
5. `success`
6. `ip`
7. `user_agent`
8. `created_at`
当前已落地的 `scene` 基线:
1. `login`
2. `bind_phone`
3. `change_phone`
当前已落地的 `action` 基线:
1. `send_code`
2. `verify_code`
## 3. 表职责边界
### 3.1 `sms_auth_event` 负责
1. 记录短信验证码发送成功事件
2. 记录短信验证码校验成功或失败事件
3. 作为手机号维度与 IP 维度的短信鉴权统计源
4. 为发送频控、失败次数限制、captcha 触发、风险保护提供统一统计基础
### 3.2 它不负责
1. 保存验证码明文或验证码 hash
2. 保存阿里云短信 provider 的完整原始响应
3. 记录当前风险保护是否仍生效
4. 记录账号安全动作的长期审计历史
### 3.3 与其他表的边界
1. `sms_auth_event` 负责“发生过哪些短信发送/校验事件”
2. `auth_risk_block` 负责“经过统计后当前是否处于保护状态”
3. `auth_audit_log` 负责“账号维度发生过哪些安全动作”
## 4. 访问级别
`sms_auth_event` 固定为 `private table`
原因:
1. 原始手机号、原始 IP、原始 UA 都属于敏感安全数据
2. 这张表主要服务于后端风控与统计,不直接对前端暴露
3. 前端不应该直接查询或订阅短信验证码操作明细
## 5. 字段设计
| 字段名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `sms_event_id` | `String` | 是 | 主键,建议继续沿用 `smsev_*` 前缀。 |
| `phone_e164` | `String` | 是 | 标准化后的 `E.164` 手机号。 |
| `scene` | `String` | 是 | 业务场景,枚举固定为 `login``bind_phone``change_phone`。 |
| `action` | `String` | 是 | 动作类型,枚举固定为 `send_code``verify_code`。 |
| `success` | `bool` | 是 | 当前动作是否成功。 |
| `ip` | `Option<String>` | 否 | 请求来源 IP缺失时为 `null`。 |
| `user_agent` | `Option<String>` | 否 | 请求来源 UA缺失时为 `null`。 |
| `created_at` | `String` | 是 | 事件发生时间UTC RFC3339。 |
补充约束:
1. 当前阶段不存 `provider_request_id`,因为这张表不是供应商排障日志表。
2. 当前阶段不存 `provider_code``provider_message`,避免把供应商响应日志职责压进统计源表。
3. 当前阶段不存 `user_id`,因为验证码发送和校验发生时,场景并不总是已经稳定归属到某个正式账号。
## 6. 枚举与语义设计
### 6.1 `scene`
当前阶段固定只支持:
1. `login`
2. `bind_phone`
3. `change_phone`
解释:
1. `login` 对应手机号验证码登录
2. `bind_phone` 对应微信待激活账号绑定手机号
3. `change_phone` 对应正式账号更换手机号
### 6.2 `action`
当前阶段固定只支持:
1. `send_code`
2. `verify_code`
解释:
1. `send_code` 表示验证码已成功发出
2. `verify_code` 表示一次验证码校验尝试已经结束
### 6.3 `success`
固定语义:
1. `send_code + success = true` 表示供应商确认发送成功
2. `verify_code + success = true` 表示验证码校验通过
3. `verify_code + success = false` 表示验证码校验失败或已失效
当前阶段特别约束:
1. `send_code + success = false` 暂不入表
2. 发送失败继续由应用日志、供应商日志与 tracing 承担排障,不混入当前统计口径
这样做的原因是:
1. 现有发送频控按 `action = send_code` 的总数统计
2. Node 当前只在发送成功后写事件
3. 若直接把发送失败也写入,会改变当前频控语义
## 7. 统计口径设计
### 7.1 发送频控
固定统计来源:
1. 按手机号统计 `action = send_code` 的事件数
2. 按 IP 统计 `action = send_code` 的事件数
当前 Node 兼容窗口:
1. 单手机号:`过去 1 天`
2. 单 IP`过去 1 小时`
### 7.2 验证失败次数限制
固定统计来源:
1. 按手机号统计 `action = verify_code AND success = false`
2. 按 IP 统计 `action = verify_code AND success = false`
当前 Node 兼容窗口:
1. 单手机号:`过去 1 小时`
2. 单 IP`过去 1 小时`
### 7.3 captcha 触发
固定统计来源:
1. 按手机号统计 `verify_code` 失败次数
2. 按 IP 统计 `verify_code` 失败次数
说明:
1. `captcha challenge` 自身不写进 `sms_auth_event`
2. `captcha_required` 审计事件继续写进 `auth_audit_log`
### 7.4 风险保护触发
固定统计来源:
1. 按手机号统计 `verify_code` 失败次数
2. 按 IP 统计 `verify_code` 失败次数
说明:
1. 风险保护命中后真正的当前态写进 `auth_risk_block`
2. `sms_auth_event` 只提供统计基础,不直接承载保护状态
## 8. 写入规则
### 8.1 `POST /api/auth/phone/send-code`
固定流程:
1. 先检查当前手机号 / 当前 IP 是否存在活跃 `auth_risk_block`
2. 再根据 `sms_auth_event` 统计发送频控
3. 再根据 `sms_auth_event` 统计决定是否需要 captcha
4. 短信 provider 返回发送成功后,写入一条:
- `scene = 当前请求场景`
- `action = send_code`
- `success = true`
### 8.2 `POST /api/auth/phone/login`
固定流程:
1. 先检查当前手机号 / 当前 IP 是否存在活跃 `auth_risk_block`
2. 再根据 `sms_auth_event` 统计失败次数限制
3. 校验成功时写入:
- `scene = login`
- `action = verify_code`
- `success = true`
4. 校验失败时写入:
- `scene = login`
- `action = verify_code`
- `success = false`
5. 校验失败写入完成后,再按统计结果决定是否触发 `auth_risk_block`
### 8.3 `POST /api/auth/wechat/bind-phone`
固定流程:
1. 与手机号登录复用同一套校验前检查
2. 校验成功写入 `bind_phone + verify_code + success = true`
3. 校验失败写入 `bind_phone + verify_code + success = false`
4. 校验失败后再决定是否触发 `auth_risk_block`
### 8.4 `POST /api/auth/phone/change`
固定流程:
1. 与手机号登录复用同一套校验前检查
2. 校验成功写入 `change_phone + verify_code + success = true`
3. 校验失败写入 `change_phone + verify_code + success = false`
4. 校验失败后再决定是否触发 `auth_risk_block`
## 9. 查询索引与统计要求
### 9.1 必须具备的唯一约束
1. `sms_event_id` 主键唯一
### 9.2 必须具备的查询索引
1. `(phone_e164, action, created_at DESC)`
作用:支撑按手机号统计发送次数与失败次数
2. `(ip, action, created_at DESC)`
作用:支撑按 IP 统计发送次数与失败次数
3. `(phone_e164, action, success, created_at DESC)`
作用:支撑按手机号统计 `verify_code` 失败窗口
4. `(ip, action, success, created_at DESC)`
作用:支撑按 IP 统计 `verify_code` 失败窗口
说明:
1. 当前阶段不强求单独按 `scene` 建主索引,因为已有统计主要按手机号/IP 与动作窗口展开。
2. `scene` 继续作为事件上下文字段保留,便于后续如果要细分某一场景的频控,再追加索引。
## 10. 读取规则
当前阶段 `sms_auth_event` 不直接对外暴露 DTO。
它只支撑后端内部这几类聚合查询:
1. `count_since_by_phone(phone_e164, action, success?, since)`
2. `count_since_by_ip(ip, action, success?, since)`
读取约束:
1. `ip = null` 时,按 IP 统计固定返回 `0`
2. 统计窗口由 Axum 应用层提供,不把“过去 1 小时”“过去 1 天”写死进表层
3. 表层只提供原子计数,不在表层拼装“是否需要 captcha / 是否需要封禁”的业务判断
## 11. 与其他鉴权表的协作规则
### 11.1 与 `auth_risk_block`
固定规则:
1. `sms_auth_event` 先累积失败事实
2. Axum 根据失败统计决定是否写入或刷新 `auth_risk_block`
3. 不能直接从 `auth_risk_block` 反推历史失败次数
### 11.2 与 `auth_audit_log`
固定规则:
1. `sms_auth_event` 不承担安全操作审计展示职责
2. `captcha_required``risk_block_phone``risk_block_ip` 等用户可见安全事件,继续写进 `auth_audit_log`
3. 不允许为了省表而把 `sms_auth_event` 直接拿去充当账号操作记录
## 12. 与当前 Node `sms_auth_events` 的映射关系
| Node 列 | 新字段 | 迁移规则 |
| --- | --- | --- |
| `id` | `sms_event_id` | 原样迁移。 |
| `phone_number` | `phone_e164` | 重命名迁移,值原样保留。 |
| `scene` | `scene` | 原样迁移。 |
| `action` | `action` | 原样迁移。 |
| `success` | `success` | 原样迁移。 |
| `ip` | `ip` | 原样迁移。 |
| `user_agent` | `user_agent` | 原样迁移。 |
| `created_at` | `created_at` | 原样迁移。 |
## 13. reducer / service 落地约束
### 13.1 `module-auth` reducer 层
必须至少具备:
1. `append_sms_auth_event`
说明:
1. 这是一张追加型统计源表,不做就地更新。
2. 后续若要做归档或清理,再单独增加 maintenance reducer 或离线清理任务。
### 13.2 Axum 应用层
固定负责:
1. 标准化手机号为 `E.164`
2. 组织 `scene``action``success`
3. 在正确的时机写入事件
4. 基于统计结果决定是否触发 captcha 与 `auth_risk_block`
## 14. 不允许的设计漂移
后续实现时禁止出现以下情况:
1. 把验证码明文、验证码 hash 写进 `sms_auth_event`
2. 把阿里云 provider 的完整响应 JSON 直接写进 `sms_auth_event`
3. 把当前是否被保护的状态写进 `sms_auth_event`
4. 发送失败也无条件入表,却继续沿用当前“按 `send_code` 总量限流”的统计口径
5. 为了展示账号安全记录,直接把 `sms_auth_event` 暴露给前端
## 15. 本任务完成定义
当以下条件满足时,`设计 sms_auth_event` 视为完成:
1. 发送与校验事件的写入范围已经固定。
2. 当前发送频控、失败限制、captcha 触发与风险保护的统计口径已经固定。
3. 已和 `auth_risk_block``auth_audit_log` 明确切开职责。
4. 后续可以直接按这份文档编码 reducer、计数查询与 Axum 应用层判断逻辑。
## 16. 依据文件
1. `server-node/src/repositories/smsAuthEventRepository.ts`
2. `server-node/src/auth/authService.ts`
3. `server-node/src/services/smsVerificationService.ts`
4. `server-node/src/routes/authRoutes.ts`
5. `server-node/src/db/migrations.ts`
6. `packages/shared/src/contracts/auth.ts`
7. `docs/prd/ACCOUNT_SYSTEM_AND_LOGIN_ENTRY_PRD_2026-04-09.md`
8. `docs/prd/MY_TAB_SETTINGS_AND_SECURITY_PRD_2026-04-16.md`
9. `docs/technical/SPACETIMEDB_AUTH_AUDIT_LOG_TABLE_DESIGN_2026-04-21.md`
10. `docs/technical/SPACETIMEDB_AUTH_RISK_BLOCK_TABLE_DESIGN_2026-04-21.md`