feat: complete registration invitation and auth redesign
This commit is contained in:
+40
-17
@@ -165,7 +165,9 @@ WebSocket Upgrade 阶段由 `server/hdl_websock.go` 先调用 `checkAPIKey`。
|
||||
{"login":{"id":"login-2","scheme":"token","secret":"<上次登录响应原样返回的 token 字符串>"}}
|
||||
```
|
||||
|
||||
`basic.secret` 是 `base64(username + ":" + password)`。Token 登录必须把上次 `params.token` 的 JSON 字符串原样填入 `secret`,不能再次 Base64 编码。登录成功 `{ctrl}` 的 `params` 至少包含 `user`、`authlvl`、`token`、`expires`。产品 HTTP 也使用同一个 `params.token`:`Authorization: Bearer <params.token>`,客户端不要解码或二次编码。Token 对客户端是不可解析的 opaque 字符串,只能存入系统安全存储。`id` 只用于匹配本次 Session 的命令响应,不是业务幂等键。
|
||||
`basic.secret` 是 `base64(username + ":" + password)`。Base64 只是协议编码,不是加密,Basic 注册和登录必须使用 HTTPS/WSS;服务端解析密码后使用 bcrypt 哈希,数据库不保存密码明文。Token 登录必须把上次 `params.token` 的 JSON 字符串原样填入 `secret`,不能再次 Base64 编码。登录成功 `{ctrl}` 的 `params` 至少包含 `user`、`authlvl`、`token`、`expires`。产品 HTTP 也使用同一个 `params.token`:`Authorization: Bearer <params.token>`,客户端不要解码或二次编码。Token 对客户端是不可解析的 opaque 字符串,只能存入系统安全存储。`id` 只用于匹配本次 Session 的命令响应,不是业务幂等键。
|
||||
|
||||
当前登录 Token 默认有效期为 14 天,不单独设置 Refresh Token 或刷新接口。每次 Basic 登录或未过期 Token 登录成功后,服务端都会返回一个新的 14 天 Token;客户端必须原子覆盖安全存储中的旧 `token/expires`。Token 已过期或校验失败时不能续期,必须回到账号密码登录。
|
||||
|
||||
`hi` 成功示例:
|
||||
|
||||
@@ -295,7 +297,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
1. 创建、提交、同意、拒绝、删除、转让、收藏、转发等写请求必须携带 `Idempotency-Key`。
|
||||
2. GET、断线同步和同幂等键写请求可自动重试;密码、验证码和风控确认只按接口返回的 `retry_after_seconds` 重试。
|
||||
3. `401 TOKEN_EXPIRED` 只允许一次静默刷新;刷新失败必须退出登录。`403` 不重试,`429` 按服务端退避。
|
||||
3. `401 TOKEN_EXPIRED` 不执行刷新接口,立即清理失效 Token 并返回账号密码登录;其他 `401` 也不得无限重试。`403` 不重试,`429` 按服务端退避。
|
||||
4. 服务端必须在最终写入前再次校验好友关系、群角色、禁言、消息归属、文件 ACL、租户配额和风控,不能信任前端按钮状态。
|
||||
5. 普通用户禁止向系统 Topic 发布;系统通知必须由服务端受控身份生成。
|
||||
|
||||
@@ -350,10 +352,11 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
**状态:基本账号密码登录已实现;WS Token 可复用于产品 HTTP。**
|
||||
|
||||
- 前端动作:账号、密码非空且已勾选协议后启用登录;密码仅驻留当前页面内存。
|
||||
- 协议:已完成 `hi` 的 WebSocket 发送 `{login scheme:"basic"}`,`secret=base64(username:password)`;不得先登录再传企业码。
|
||||
- 协议:已完成 `hi` 的 WebSocket 发送 `{login scheme:"basic"}`,`secret=base64(username:password)`;Base64 不是加密,必须通过 WSS 传输,不得先登录再传企业码。
|
||||
- 成功:读取 `{ctrl params.user/token/expires/authlvl}`,Token 存 Keychain/Keystore;立即 `sub me`,再加载会话。前端不解码 Token,也不再次 Base64 编码。
|
||||
- 产品 HTTP:使用同一个 `params.token` 作为 `Authorization: Bearer <params.token>`;所有 `/v1/im/*` HTTP 请求仍必须带 `X-Tinode-APIKey`、`X-IM-Schema: 1`,写请求再带 `Idempotency-Key`。
|
||||
- 待改造:后续可演进为独立短期 access token 与可轮换 refresh token;当前版本先复用 Tinode 登录 Token,并已绑定 `tenant_id/user_id/auth_version`。修改密码、封禁账号后旧 Token 应失效。
|
||||
- Token:当前复用 Tinode 单一登录 Token,默认有效期 14 天,不使用 Refresh Token。有效 Token 登录成功会签发新的 14 天 Token,客户端覆盖旧值;过期后重新账号密码登录。Token 已绑定 `tenant_id/user_id/auth_version`,修改密码、封禁账号后旧 Token 应失效。
|
||||
- 密码:服务端使用 `bcrypt.DefaultCost=10` 生成单向哈希,`im_auth.secret` 只保存哈希;客户端和服务端不得记录密码或完整 Basic `secret`。
|
||||
- 错误:`AUTH_FAILED` 统一显示账号或密码错误;`TENANT_MISMATCH` 清 Token 并回企业码页;`ACCOUNT_DISABLED`、`REALNAME_REQUIRED`、`YOUTH_RESTRICTED` 按专用页面处理。
|
||||
|
||||
登录成功示例:
|
||||
@@ -391,18 +394,35 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
<img src="images/注册页面.png" alt="注册页面" width="360">
|
||||
|
||||
**状态:Tinode `{acc}` 基础注册和验证码预校验已实现;短信供应商接入前固定测试码为 `123456`。**
|
||||
**状态:注册原型和服务端注册分流均已完成;手机号非空时优先走手机号方式,服务端已接入 username、邀请码和短信开关逻辑。**
|
||||
|
||||
- 获取验证码:调用 `POST /v1/im/auth/verification-codes`,必须带 `X-Tinode-APIKey`、`X-Tenant-Code`、`X-IM-Schema: 1`,Body `{"scene":"register","phone":"+86138..."}`;成功返回 `challenge_id`、`expires_in_seconds`、`resend_after_seconds`。
|
||||
- 提交注册:前端校验密码一致和协议后,先调用 `POST /v1/im/auth/verification-codes/{challenge_id}/verify`,Body `{"code":"123456"}`,获得一次性 `verification_token`。
|
||||
- WebSocket:已完成同一企业 `hi` 后发送 `{acc}`。把一次性验证结果放在 `{acc}.cred[0].resp`,它必须是 HTTP verify 返回的 `verification_token`,不是短信验证码 `123456`。不得写入 `desc.private`,也不得在 `{acc}` 顶层传 `tags`。服务端会把已校验手机号写入 `im_users.phone`,并保留验证凭证记录;用户标签已废弃,客户端设置 `tags` 会返回 `403 params.what=tags`。
|
||||
- 手机号格式:HTTP 发码、验证码校验、`{acc}.secret` 中的 username、`cred.val` 必须统一使用 E.164 格式,如中国手机号 `+86138...`。服务端默认按 `CN` 解析未带国家码的号码并规范化,但客户端不得依赖该兼容行为;数据库 `im_users.phone` 始终保存带 `+` 和国家码的 E.164 值。
|
||||
- 页面规则:手机号非空时以手机号方式为准并忽略用户名输入;只有手机号为空时才使用用户名。两个字段同时为空时拒绝注册。
|
||||
- 用户名格式:普通用户名必须匹配 `^[a-z0-9]{1,10}$`,即 1~10 位小写英文字母或数字;允许纯数字,不允许大写、中文、空格、点、下划线和其他符号。前端限制输入,服务端按原始值再次校验,失败返回 `USERNAME_INVALID_FORMAT`。
|
||||
- 邀请码格式:非空时必须匹配 `^[A-Za-z]{5}$`,固定 5 位大小写英文字母并区分大小写;客户端不得自动转换大小写。服务端在当前租户内按明文精确查询。
|
||||
- 邀请码可复用:同一有效邀请码可以供多个用户注册,使用后仍保持 `active`;服务端为每个注册用户记录邀请来源。只有到期或管理员撤销后才不可继续使用。
|
||||
- 邀请码主数据位于 HotGo 控制库并按 `tenant_id` 隔离;注册使用明细位于当前租户业务库。客户端契约不受分库影响,仍只提交完整 5 位邀请码。
|
||||
- 用户名方式:手机号为空时提交用户名、密码和邀请码,不发送短信。邀请码必须有效,注册后 `im_users.username` 保存用户名,`im_users.phone=NULL`。手机号非空时忽略用户名输入,也不校验被忽略用户名的格式。
|
||||
- 找回提示:用户名方式提交注册前,客户端明确提示“未绑定手机号时无法自助找回密码,忘记密码需联系企业管理员重置”。
|
||||
- 手机号方式:前端只接受 11 位数字。调用 `POST /v1/im/auth/verification-codes`,Body `{"scene":"register","phone":"138..."}`;后端补 `+86` 后发送,成功返回 `challenge_id`、`expires_in_seconds`、`resend_after_seconds`。
|
||||
- 短信开关开启:手机号方式必须完成验证码验证,邀请码不能绕过短信验证;验证成功后用户名字段和手机号字段均保存规范化的 E.164 手机号。
|
||||
- 短信开关关闭:发码接口仍返回正常倒计时但不调用短信服务商。手机号方式提供有效邀请码后可无验证码注册,规范化手机号同时写入 `username` 和 `phone`,但不建立已完成的 `tel credential`。
|
||||
- 提交注册:`{acc}` 增加独立的 `username`、`phone` 和 `invite_code`。短信验证成功后调用 `POST /v1/im/auth/verification-codes/{challenge_id}/verify` 获得一次性 `verification_token`。`secret` 使用最终选择值与密码。
|
||||
- 服务端判定:`phone` 非空时忽略 `username`,并校验 `secret` 登录名与规范化手机号一致;`phone` 为空时使用 `username` 并校验 `^[a-z0-9]{1,10}$`。普通用户名允许纯数字,因此不能只根据 `secret` 是否为 11 位数字推断注册方式。
|
||||
- 禁止客户端提交 `tags`;手机号方式始终写入 `im_users.phone`,只有短信验证成功时才建立已完成的电话凭证。
|
||||
|
||||
用户名方式:
|
||||
|
||||
```json
|
||||
{"acc":{"id":"acc-1","user":"new","scheme":"basic","secret":"base64(+86138...:password)","login":true,"cred":[{"meth":"tel","val":"+86138...","resp":"verification_token","params":{"challenge_id":"challenge-id"}}],"desc":{"public":{"fn":"用户昵称"}}}}
|
||||
{"acc":{"id":"acc-1","user":"new","scheme":"basic","username":"alice","phone":"","secret":"base64(alice:password)","invite_code":"AbCdE","login":true,"cred":[]}}
|
||||
```
|
||||
|
||||
手机号验证方式:
|
||||
|
||||
```json
|
||||
{"acc":{"id":"acc-1","user":"new","scheme":"basic","username":"ignored-if-present","phone":"138...","secret":"base64(+86138...:password)","invite_code":"","login":true,"cred":[{"meth":"tel","val":"138...","resp":"verification_token","params":{"challenge_id":"challenge-id"}}]}}
|
||||
```
|
||||
- 成功:返回新 `user` 和登录 Token;客户端保存 `params.token`,随后执行 `sub me` 和会话恢复。创建用户前原子校验 `im_tenant_config.max_users`,超限返回 `TENANT_USER_QUOTA_EXCEEDED`。
|
||||
- 安全:手机号在租户内唯一;验证码只保存摘要、单次消费;密码按服务端策略校验并使用强哈希保存。
|
||||
- 安全:用户名、手机号分别在租户内唯一;验证码票据单次消费,邀请码在有效期内可重复使用;密码按服务端策略校验并使用强哈希保存。
|
||||
|
||||
### 1.5 注册页面-倒计时.png
|
||||
|
||||
@@ -420,11 +440,14 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
**状态:新增。**
|
||||
|
||||
- 适用范围:仅 `im_users.phone` 非空的账号支持短信自助找回。`phone=NULL` 的用户名账号不提供自助找回,客户端提示联系当前企业管理员重置密码。
|
||||
- 发码:`POST /v1/im/auth/verification-codes`,Body `{"scene":"reset_password","phone":"..."}`。无论账号是否存在都返回相同受理响应。
|
||||
- 校验:`POST /v1/im/auth/password-reset/challenges/{challenge_id}/verify`,Body `{"code":"123456"}`,成功返回短时、单次使用的 `reset_token`。
|
||||
- 重置:`POST /v1/im/auth/password-reset/confirm`,Body `{"reset_token":"...","new_password":"..."}`,带幂等键。
|
||||
- 成功:服务端更新密码、递增 `auth_version`、撤销该用户全部 access/refresh token 和在线 Session;客户端清理本租户凭证并返回登录页。
|
||||
- 凭证补建:只要账号的 `phone` 非空,成功完成 `reset_password` 短信挑战即可证明当前持有该号码;即使注册时未建立 `tel credential`,本次成功后也可补建完成状态的电话凭证。
|
||||
- 成功:服务端更新密码、递增 `auth_version`,使该用户全部旧登录 Token 失效并撤销在线 Session;客户端清理本租户凭证并返回登录页。
|
||||
- 失败:`PASSWORD_POLICY_VIOLATION` 返回规则明细;验证码错误不返回账号存在性,达到上限后冻结 challenge。
|
||||
- 管理员路径:无手机号账号只能由当前租户管理员调用 `POST /admin/v1/tenant/users/{user_id}/reset-password` 重置;不得根据用户名跨租户查找或操作。
|
||||
|
||||
### 1.7 身份验证(安卓).png
|
||||
|
||||
@@ -889,7 +912,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
**状态:新增。**
|
||||
|
||||
- 获取二维码:`POST /v1/im/me/qrcodes`,Body `{"purpose":"add_friend","expires_in_seconds":86400}`,返回 `qr_token,qr_payload,expires_at,display_profile`。
|
||||
- 二维码只含不透明、签名、带租户和用途约束的短期 Token,不直接放手机号、用户 ID 或 access token。
|
||||
- 二维码只含不透明、签名、带租户和用途约束的短期 Token,不直接放手机号、用户 ID 或登录 Token。
|
||||
- 分享/保存图片是客户端动作;扫码解析始终调用 5.3,过期返回 `QR_CODE_EXPIRED`,篡改返回 `QR_CODE_INVALID`。
|
||||
|
||||
### 5.5 添加好友-输入精准用户ID.png
|
||||
@@ -1363,7 +1386,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
- 下载任务保存 `file_id,etag,total_bytes,downloaded_bytes,temp_path,state`;恢复时携带 `Range` 与 `If-Range: <etag>`。
|
||||
- ETag 改变或服务端返回完整 200 时丢弃旧分片重新下载;取消时关闭连接并清理临时文件。
|
||||
- Token 过期先完成一次刷新后续传,不能把 Token 写入任务文件或日志。
|
||||
- 下载中断后,如果客户端通过有效 Token 登录取得了新 Token,则使用新 Token 和原 Range 状态续传;Token 已过期或失效时暂停任务并返回登录页。不能把 Token 写入任务文件或日志。
|
||||
|
||||
### 7.11 文件-下载完成.png
|
||||
|
||||
@@ -1727,7 +1750,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
**状态:菜单纯客户端;数据接口部分新增。**
|
||||
|
||||
- 菜单来自 `bootstrap.settings_menu`,安全、通知、隐私、通用、关于按权限展示。系统通知权限状态由 iOS API 读取,不由服务端伪造。
|
||||
- 退出:`POST /v1/im/auth/logout` 撤销当前 refresh/access token 和设备会话,成功或 Token 已失效后关闭 WS、清安全存储和本租户用户缓存。
|
||||
- 退出:`POST /v1/im/auth/logout` 撤销当前登录 Token 对应的设备会话,成功或 Token 已失效后关闭 WS、清安全存储和本租户用户缓存。
|
||||
- 多设备退出使用 `DELETE /v1/im/security/sessions/{session_id}`,需二次验证。
|
||||
|
||||
### 10.8 设置(安卓).png
|
||||
@@ -1930,7 +1953,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
| 优先级 | 改造项 | 阻塞图片章节 |
|
||||
| --- | --- | --- |
|
||||
| P0 | `/v1/im` 统一认证中间件、响应封装、幂等键、租户 Principal | 全部 HTTP 产品能力 |
|
||||
| P0 | 企业码解析、验证码、密码重置、二次认证、Token `auth_version` 和刷新/撤销 | 1.1~1.11、10.7~10.8 |
|
||||
| P0 | 企业码解析、验证码、密码重置、二次认证、Token `auth_version`、登录换新和撤销 | 1.1~1.11、10.7~10.8 |
|
||||
| P0 | 普通用户禁止发布系统 Topic;系统事件可信生产通道 | 2.5、3.1~3.5、6.4、11.1 |
|
||||
| P0 | 消息 `x-im-schema/type/client-mid` 校验和服务端幂等 | 2~4 章 |
|
||||
| P0 | 文件资源级 ACL、Range、ETag、用途绑定和租户存储配额 | 3.6、4.10~4.14、6.27~7.16、11.4~11.5 |
|
||||
@@ -1954,7 +1977,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
| T-07 | 普通群成员调用管理员、禁言、转让群主接口 | 返回 `FORBIDDEN`,群状态不变且写安全审计 |
|
||||
| T-08 | 群成员数已达租户配置上限时同意两个并发申请 | 至多一个成功,成员数不超限 |
|
||||
| T-09 | 非消息接收者持同租户 Token 下载文件 | 返回 `FILE_FORBIDDEN`,不能只通过 file_id 下载 |
|
||||
| T-10 | 文件下载中 Token 过期后刷新并 Range 续传 | 文件完整、checksum 正确、不重复下载已完成分片 |
|
||||
| T-10 | 文件下载中断后以有效 Token 登录取得新 Token 并 Range 续传 | 文件完整、checksum 正确、不重复下载已完成分片 |
|
||||
| T-11 | 风控二次确认后重复重放 risk token | 只有首次有效,其余返回 Token 已消费 |
|
||||
| T-12 | 切换企业后查看缓存、上传任务、收藏和搜索 | 不出现上一租户数据或任务 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user