forked from wangziqi/gongxue-base
feat: support Aliyun PNVS SMS verification
This commit is contained in:
@@ -26,7 +26,7 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先
|
||||
|
||||
## 当前已实现
|
||||
|
||||
- `POST /api/auth/sms/send`:手机号验证码发送,验证码只保存 HMAC hash。
|
||||
- `POST /api/auth/sms/send`:手机号验证码发送。传统短信 provider 的验证码只保存 HMAC hash;阿里云 PNVS 短信认证 provider 由阿里云生成并核验验证码,本地只保存发送流水、`outId`、频控和审计。
|
||||
- `POST /api/auth/sms/verify`:验证码登录,自动创建或复用 `platform_users`。
|
||||
- `GET /api/auth/me`:通过 Bearer token 获取当前用户。
|
||||
- `POST /api/auth/logout`:吊销迁移期 session。
|
||||
@@ -38,6 +38,7 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先
|
||||
- `auth_login_events`:登录事件审计。
|
||||
- `app_private.auth_sessions`:迁移期 session token hash。
|
||||
- 阿里云短信 `SendSms` provider:使用租户级 AccessKey、签名、模板发送。
|
||||
- 阿里云 PNVS 短信认证 `SendSmsVerifyCode` / `CheckSmsVerifyCode` provider:使用租户级 AccessKey、签名、模板发送和核验验证码,适合不想走传统短信模板资质链路的手机号登录场景。
|
||||
- 腾讯云短信 `SendSms` provider:使用租户级 SecretId/SecretKey、SdkAppId、签名、模板发送。
|
||||
- `POST /api/commerce/payments/create`:按订单创建微信支付 JSAPI 或支付宝 WAP 支付参数。
|
||||
- `POST /api/commerce/payments/notify/wechat_pay`:微信支付 API v3 通知验签、AES-GCM 解密、幂等落库和权益开通。
|
||||
@@ -45,11 +46,12 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先
|
||||
|
||||
## 短信 Provider
|
||||
|
||||
本地默认是 `AUTH_SMS_PROVIDER=mock`,仅开发环境返回 `debugCode`。生产环境只允许 `aliyun/aliyun-sms` 或 `tencent/tencent-sms`;如果仍为 mock 或写成未知 provider,API 启动和 `readiness:production` 都会直接失败。
|
||||
本地默认是 `AUTH_SMS_PROVIDER=mock`,仅开发环境返回 `debugCode`。生产环境只允许 `aliyun/aliyun-sms`、`aliyun-pnvs/aliyun-sms-auth` 或 `tencent/tencent-sms`;如果仍为 mock 或写成未知 provider,API 启动和 `readiness:production` 都会直接失败。
|
||||
|
||||
真实 provider:
|
||||
|
||||
- `aliyun` / `aliyun-sms`:阿里云短信 `SendSms`,需要 AccessKey、签名、模板 ID。
|
||||
- `aliyun-pnvs` / `aliyun-sms-auth`:阿里云 PNVS 短信认证服务,调用 `SendSmsVerifyCode` 发送,调用 `CheckSmsVerifyCode` 核验;验证码由阿里云生成并校验,本地不保存明文验证码。
|
||||
- `tencent` / `tencent-sms`:腾讯云短信 `SendSms`,需要 SecretId、SecretKey、SdkAppId、签名、模板 ID。
|
||||
|
||||
密钥策略:
|
||||
@@ -60,6 +62,88 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先
|
||||
- provider endpoint 默认使用官方域名,生产环境只允许 HTTPS 官方域名;本地测试可使用 `localhost/127.0.0.1` fake server。
|
||||
- `readiness:production:db` 会检查 active/testing 短信 provider 的 `signName/templateCode` 或 `smsSdkAppId/signName/templateId`,并阻断公开配置里的 secret-like 字段。
|
||||
|
||||
### 阿里云 PNVS 短信认证配置示例
|
||||
|
||||
`AUTH_SMS_PROVIDER` 可设为 `aliyun-pnvs`,租户公开配置写 `tenant_auth_providers.config_public`,AccessKey 写 `app_private.tenant_secrets`。PNVS 模板参数推荐使用阿里云占位 `##code##`,如 `{"code":"##code##","min":"5"}`;后端会用 `AUTH_CODE_TTL_SECONDS` 自动补 `min`。
|
||||
|
||||
```json
|
||||
{
|
||||
"provider": "aliyun-pnvs",
|
||||
"status": "active",
|
||||
"configPublic": {
|
||||
"signName": "工学教育",
|
||||
"templateCode": "SMS_123456789",
|
||||
"endpoint": "https://dypnsapi.aliyuncs.com",
|
||||
"regionId": "cn-hangzhou",
|
||||
"templateParam": {
|
||||
"code": "##code##",
|
||||
"min": "5"
|
||||
},
|
||||
"codeLength": "6",
|
||||
"validTime": "300",
|
||||
"interval": "60",
|
||||
"duplicatePolicy": "1",
|
||||
"secretRef": "app_private.tenant_secrets:sms:aliyun-pnvs"
|
||||
},
|
||||
"secret": {
|
||||
"secretScope": "sms",
|
||||
"secretKey": "aliyun-pnvs",
|
||||
"secretJson": {
|
||||
"accessKeyId": "LTAI********",
|
||||
"accessKeySecret": "********"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
生产 bootstrap SQL 参考:
|
||||
|
||||
```sql
|
||||
insert into app_private.tenant_secrets (
|
||||
tenant_id, secret_scope, secret_key, provider, secret_json, last_rotated_at
|
||||
)
|
||||
values (
|
||||
:'tenant_id'::uuid,
|
||||
'sms',
|
||||
'aliyun-pnvs',
|
||||
'aliyun-pnvs',
|
||||
jsonb_build_object('accessKeyId', :'access_key_id', 'accessKeySecret', :'access_key_secret'),
|
||||
now()
|
||||
)
|
||||
on conflict (tenant_id, secret_scope, secret_key)
|
||||
do update set provider = excluded.provider,
|
||||
secret_json = excluded.secret_json,
|
||||
last_rotated_at = now(),
|
||||
updated_at = now();
|
||||
|
||||
insert into public.tenant_auth_providers (
|
||||
tenant_id, provider, status, display_name, config_public
|
||||
)
|
||||
values (
|
||||
:'tenant_id'::uuid,
|
||||
'aliyun-pnvs',
|
||||
'active',
|
||||
'阿里云短信认证',
|
||||
jsonb_build_object(
|
||||
'signName', :'sign_name',
|
||||
'templateCode', :'template_code',
|
||||
'endpoint', 'https://dypnsapi.aliyuncs.com',
|
||||
'regionId', 'cn-hangzhou',
|
||||
'templateParam', jsonb_build_object('code', '##code##', 'min', '5'),
|
||||
'codeLength', '6',
|
||||
'validTime', '300',
|
||||
'interval', '60',
|
||||
'duplicatePolicy', '1',
|
||||
'secretRef', 'app_private.tenant_secrets:sms:aliyun-pnvs'
|
||||
)
|
||||
)
|
||||
on conflict (tenant_id, provider)
|
||||
do update set status = excluded.status,
|
||||
display_name = excluded.display_name,
|
||||
config_public = excluded.config_public,
|
||||
updated_at = now();
|
||||
```
|
||||
|
||||
### 阿里云短信配置示例
|
||||
|
||||
```json
|
||||
|
||||
Reference in New Issue
Block a user