feat: add china auth providers

This commit is contained in:
Codex
2026-06-28 22:19:46 +08:00
parent 523b63c53b
commit dee82e670f
11 changed files with 1011 additions and 26 deletions

View File

@@ -19,31 +19,109 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先
- `POST /api/auth/sms/verify`:验证码登录,自动创建或复用 `platform_users`
- `GET /api/auth/me`:通过 Bearer token 获取当前用户。
- `POST /api/auth/logout`:吊销迁移期 session。
- `POST /api/auth/oauth/wechat``/wechat-miniapp``/qq`provider 占位,已固定错误码 `PROVIDER_NOT_CONFIGURED`
- `POST /api/auth/oauth/wechat-miniapp`:微信小程序 `code2Session` 登录,后端换取 openid/unionid签发 `tk_` session
- `POST /api/auth/oauth/wechat``/qq`provider 占位,已固定 `PROVIDER_NOT_CONFIGURED` / `PROVIDER_NOT_IMPLEMENTED`
- `tenant_auth_providers`:租户级公开认证配置。
- `sms_verification_codes`:验证码审计表,不保存明文 code。
- `auth_login_events`:登录事件审计。
- `app_private.auth_sessions`:迁移期 session token hash。
- 阿里云短信 `SendSms` provider使用租户级 AccessKey、签名、模板发送。
- 腾讯云短信 `SendSms` provider使用租户级 SecretId/SecretKey、SdkAppId、签名、模板发送。
## 短信 Provider
本地默认是 `AUTH_SMS_PROVIDER=mock`,仅开发环境返回 `debugCode`。生产环境如果仍为 mock会直接拒绝发送。
后续真实 provider
真实 provider
- `aliyun`阿里云短信 `SendSms`,需要 AccessKey、签名、模板 ID。
- `tencent`腾讯云短信 `SendSms`,需要 SecretId、SecretKey、SdkAppId、签名、模板 ID。
- `aliyun` / `aliyun-sms`:阿里云短信 `SendSms`,需要 AccessKey、签名、模板 ID。
- `tencent` / `tencent-sms`:腾讯云短信 `SendSms`,需要 SecretId、SecretKey、SdkAppId、签名、模板 ID。
密钥策略:
- AccessKey/SecretKey 不进入 `tenant_settings.public_config`
- 租户级密钥写 `app_private.tenant_secrets(secret_scope='sms')` 或生产 Vault。
- 前端只能看到 provider 是否启用、签名展示名、隐私协议链接等非敏感配置。
- provider endpoint 默认使用官方域名,生产环境只允许 HTTPS 官方域名;本地测试可使用 `localhost/127.0.0.1` fake server。
### 阿里云短信配置示例
```json
{
"provider": "aliyun",
"status": "active",
"configPublic": {
"signName": "工学教育",
"templateCode": "SMS_123456789",
"regionId": "cn-hangzhou"
},
"secret": {
"secretScope": "sms",
"secretKey": "aliyun",
"secretJson": {
"accessKeyId": "LTAI...",
"accessKeySecret": "..."
}
}
}
```
### 腾讯云短信配置示例
```json
{
"provider": "tencent",
"status": "active",
"configPublic": {
"smsSdkAppId": "1400000000",
"signName": "工学教育",
"templateId": "123456",
"region": "ap-guangzhou",
"templateParamSet": ["{code}"]
},
"secret": {
"secretScope": "sms",
"secretKey": "tencent",
"secretJson": {
"secretId": "AKID...",
"secretKey": "..."
}
}
}
```
## 微信/QQ 登录
微信小程序登录应由前端传 `wx.login` code 到 `/api/auth/oauth/wechat-miniapp`,后端调用微信 `code2Session` 换取 openid/session_key/unionid再落 `user_identities`
已实现微信小程序登录主链路:
- 前端传 `code` 和可选 `profile`
- 后端读取租户 `wechat-miniapp` / `wechat_miniapp` provider 配置。
- `appId` 存在 `config_public``appSecret` 存在 `app_private.tenant_secrets(secret_scope='oauth')`
- `provider_subject` 使用 `appId:openid`,避免不同小程序 openid 碰撞。
- `session_key` 不返回前端,不写入公开 `raw_profile`;当前只记录 `hasSessionKey` 和更新时间标记。
- 登录成功写 `auth_login_events`,并签发 `tk_` session。
配置示例:
```json
{
"provider": "wechat-miniapp",
"displayName": "微信小程序登录",
"status": "active",
"configPublic": {
"appId": "wx...",
"envVersion": "release"
},
"secret": {
"secretScope": "oauth",
"secretKey": "wechat-miniapp",
"secretValue": "小程序 AppSecret"
}
}
```
微信网页 OAuth 和 QQ OAuth 也必须在后端完成 code 换 token、获取 openid/unionid、验错、账号合并和登录事件审计。旧 PocketBase hooks 中的邀请码/销售归属逻辑后续应拆到 `referral` feature不继续堆在 auth 模块里。
## 支付 Provider

View File

@@ -35,9 +35,10 @@
| 能力 | 状态 | 说明 |
| --- | --- | --- |
| 短信验证码登录 | 迁移期 | 已有验证码、冷却、hash、登录事件mock provider 可本地联调 |
| 短信验证码登录 | 可联调 | 已有验证码、冷却、hash、登录事件支持 mock、阿里云短信、腾讯云短信 provider生产仍需真实账号联调 |
| 迁移期 session | 迁移期 | `tk_` token hash 存在 `app_private.auth_sessions`,用户态接口已优先解析 bearer session 并拒绝伪造 userId/tenantId |
| 微信/QQ OAuth | 待补齐 | 目前是 placeholder |
| 微信小程序登录 | 可联调 | `/api/auth/oauth/wechat-miniapp` 已接 `code2Session`、openid/unionid 身份、session 签发和登录审计 |
| 微信网页/QQ OAuth | 待补齐 | 目前仍是 placeholder需要 code 换 token、回调域名、账号合并和审计 |
| 平台管理员鉴权 | 迁移期 | `x-platform-admin-key` 已可通过配置禁用;生产前必须换平台管理员 JWT/服务端会话 |
| 租户角色权限 | 可联调 | `tenant_memberships.role + permissions`,接口有权限点校验 |
| 自定义角色模板 | 待补齐 | 当前有权限 JSON 覆盖,缺角色模板、菜单/模块/字段级权限配置 UI/API |

View File

@@ -26,6 +26,8 @@
- `x-user-id` 或 body/query 的 `userId` 只允许在 `ALLOW_LEGACY_AUTH_HEADERS=true` 的本地/迁移期环境使用。
- `x-platform-admin-key` 只允许在 `ALLOW_PLATFORM_ADMIN_KEY=true` 的本地/迁移期环境使用。
- 本地短信 provider 可使用 `mock`
- 真实短信 provider 已支持阿里云和腾讯云,密钥只能从 `app_private.tenant_secrets` 读取。
- 微信小程序登录已由后端调用 `code2Session`,前端不得接触 AppSecret 或 session_key。
- `NODE_ENV=production` 下禁止 `ALLOW_LEGACY_AUTH_HEADERS=true``ALLOW_PLATFORM_ADMIN_KEY=true``AUTH_SMS_PROVIDER=mock`、默认/弱密钥和 `CORS_ORIGIN=*`
这些只允许用于本地开发和内网联调,不允许作为正式云端验收方案。
@@ -70,6 +72,7 @@ Supabase 官方允许前端用 Data API 访问数据,但前提是 RLS、最小
- 商户密钥、短信 secret、OAuth secret 不允许明文长期存储。
- 生产应使用 KMS/Vault 或 envelope encryption。
- API 只返回 `secretRef`、掩码和配置状态。
- provider endpoint 生产环境必须使用 HTTPS 官方域名;本地测试才允许 `localhost/127.0.0.1` fake server。
7. RLS 与 API 双层回归
- 数据库 RLS 要按 `tenant_id` 拦截。

View File

@@ -54,8 +54,9 @@
- 租户自有商户收款和平台代收/服务商模式。
2. 国内登录和短信
- 阿里云短信、腾讯云短信 adapter。
- 微信小程序登录、微信网页登录、QQ 登录
- 已完成阿里云短信、腾讯云短信 adapter 的后端实现和本地 fake endpoint 测试
- 已完成微信小程序 `code2Session` 登录主链路
- 继续补微信网页登录、QQ 登录、手机号绑定/换绑、真实生产账号联调。
- 旧 PocketBase 用户账号和新身份体系的映射/补绑。
3. 导入体系扩展

View File

@@ -129,7 +129,7 @@ tenant:<tenantId>:theme
| 页面 | 主要接口 |
| --- | --- |
| 启动页 | `GET /api/tenant/resolve` |
| 登录页 | `POST /api/auth/sms/send``POST /api/auth/sms/verify`、后续微信/QQ provider |
| 登录页 | `POST /api/auth/sms/send``POST /api/auth/sms/verify``POST /api/auth/oauth/wechat-miniapp`后续微信网页/QQ provider |
| 首页 | `/api/catalog/content-entries``/api/catalog/banners``/api/catalog/announcements``/api/profile/me` |
| 选地区 | `/api/catalog/regions``/api/commerce/entitlements/check` |
| 题库入口 | `/api/catalog/content-entries` |
@@ -182,6 +182,69 @@ content_entries
- 管理后台菜单按 `GET /api/tenant-admin/permissions` 和用户权限渲染。
- H5 自定义域名下要注意缓存隔离,不能把 A 租户主题缓存用到 B 租户。
## 登录对接
### 短信登录
开发环境可以先使用 mock 短信,接口会返回 `debugCode`。生产环境禁止依赖 `debugCode`
```text
POST /api/auth/sms/send
body: { "phone": "13800000000", "purpose": "login" }
POST /api/auth/sms/verify
body: { "phone": "13800000000", "code": "123456", "purpose": "login" }
```
成功后保存:
```text
session.token
session.expiresAt
user
```
后续请求统一带:
```text
Authorization: Bearer <session.token>
x-tenant-id: <tenantId>
```
### 微信小程序登录
微信小程序端调用 `Taro.login()` 获取 code然后交给后端
```text
POST /api/auth/oauth/wechat-miniapp
body: {
"code": "<wx.login code>",
"profile": {
"nickName": "...",
"avatarUrl": "..."
}
}
```
成功响应包含:
```text
provider
user
isNewUser
session.token
session.expiresAt
identity.openId
identity.unionId
```
注意:
- 前端不接触 `appSecret`
- 前端不会拿到微信 `session_key`
- 如果登录前已经解析到推广码,登录成功后再调用 `/api/referral/bind` 完成首绑保护。
- 手机号授权后续应走独立的“绑定手机号”接口,不要把微信手机号解密逻辑写在页面里。
## 第一阶段页面建议
1. `pages/bootstrap/index`