docs: design sms auth event table
This commit is contained in:
@@ -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 边界、轮换与吊销语义、索引与迁移规则。
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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`
|
||||
Reference in New Issue
Block a user