From 287c2476c689c923bb2dc80e27210f68e6048a12 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 4 Jul 2026 00:03:21 +0800 Subject: [PATCH] docs: lock production SMS on PNVS --- docs/refactor/auth-payment-provider-plan.md | 11 ++++++----- docs/refactor/next-development-todo.md | 4 ++-- docs/refactor/web-launch-acceptance-checklist.md | 2 +- scripts/aliyun-pnvs-provider-contract-test.js | 5 ++++- scripts/deploy/env/api.env.example | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/refactor/auth-payment-provider-plan.md b/docs/refactor/auth-payment-provider-plan.md index 3720d2ea..a7c7bbcf 100644 --- a/docs/refactor/auth-payment-provider-plan.md +++ b/docs/refactor/auth-payment-provider-plan.md @@ -46,13 +46,13 @@ Supabase Auth 可继续作为最终 JWT 用户体系目标;本地重构期先 ## 短信 Provider -本地默认是 `AUTH_SMS_PROVIDER=mock`,仅开发环境返回 `debugCode`。生产环境只允许 `aliyun/aliyun-sms`、`aliyun-pnvs/aliyun-sms-auth` 或 `tencent/tencent-sms`;如果仍为 mock 或写成未知 provider,API 启动和 `readiness:production` 都会直接失败。 +本地默认是 `AUTH_SMS_PROVIDER=mock`,仅开发环境返回 `debugCode`。本项目生产短信验证码登录统一使用 `AUTH_SMS_PROVIDER=aliyun-pnvs`;传统阿里云短信和腾讯云短信 adapter 仅作为兼容/回滚代码路径,不作为当前生产上线方案。如果生产仍为 mock、未知 provider,或数据库未配置匹配的 PNVS provider,API 启动、`readiness:production:db` 和上线 gate 都会失败。 真实 provider: -- `aliyun` / `aliyun-sms`:阿里云短信 `SendSms`,需要 AccessKey、签名、模板 ID。 -- `aliyun-pnvs` / `aliyun-sms-auth`:阿里云 PNVS 短信认证服务,调用 `SendSmsVerifyCode` 发送,调用 `CheckSmsVerifyCode` 核验;验证码由阿里云生成并校验,本地不保存明文验证码。 -- `tencent` / `tencent-sms`:腾讯云短信 `SendSms`,需要 SecretId、SecretKey、SdkAppId、签名、模板 ID。 +- `aliyun-pnvs` / `aliyun-sms-auth`:当前生产方案,调用 `SendSmsVerifyCode` 发送,调用 `CheckSmsVerifyCode` 核验;验证码由阿里云生成并校验,本地不保存明文验证码。 +- `aliyun` / `aliyun-sms`:阿里云短信 `SendSms` 兼容路径,需要 AccessKey、签名、模板 ID。 +- `tencent` / `tencent-sms`:腾讯云短信 `SendSms` 兼容路径,需要 SecretId、SecretKey、SdkAppId、签名、模板 ID。 PNVS 只用于手机号登录和 `bind_phone` 这类验证码认证场景,不用于营销短信、催缴短信或 CRM 触达。平台催缴里的 `channel='sms'` 是内部提醒渠道枚举;外部催缴通知当前通过 generic/钉钉/飞书/企微 webhook worker 发送,不能复用 PNVS 的验证码接口。 @@ -62,7 +62,8 @@ PNVS 只用于手机号登录和 `bind_phone` 这类验证码认证场景,不 - 租户级密钥写 `app_private.tenant_secrets(secret_scope='sms')` 或生产 Vault。 - 前端只能看到 provider 是否启用、签名展示名、隐私协议链接等非敏感配置。 - provider endpoint 默认使用官方域名,生产环境只允许 HTTPS 官方域名;本地测试可使用 `localhost/127.0.0.1` fake server。 -- `readiness:production:db` 会检查 active/testing 短信 provider 的 `signName/templateCode` 或 `smsSdkAppId/signName/templateId`,并阻断公开配置里的 secret-like 字段。 +- `npm run diagnose:aliyun-pnvs` 会只读检查生产 env、PNVS `tenant_auth_providers` 和 `tenant_secrets` 是否对齐;输出只包含密钥长度和脱敏前后缀。 +- `readiness:production:db` 会检查 active/testing PNVS provider 的 `signName/templateCode`、secretRef 和私密 `tenant_secrets`,并阻断公开配置里的 secret-like 字段。 ### 阿里云 PNVS 短信认证配置示例 diff --git a/docs/refactor/next-development-todo.md b/docs/refactor/next-development-todo.md index 50549bd8..6d7a689c 100644 --- a/docs/refactor/next-development-todo.md +++ b/docs/refactor/next-development-todo.md @@ -59,7 +59,7 @@ - 已覆盖学生、租户管理员、平台管理员、错租户、坏签名、禁用 legacy header 的 API 集成测试。 - 已补自定义角色模板、菜单/模块/字段级配置 API、班级/学生范围权限;已新增 `npm run test:rls` 本地动态 RLS 深测,覆盖主租户、合作商租户、无租户 claim、平台管理员旁路和跨租户写入拒绝;已新增 `npm run smoke:auth:remote` 用真实 Supabase access token 验收云端 Auth/JWKS 映射;继续在预生产/生产执行并留档。 - 前端联调时禁止继续使用 `x-user-id`;`x-tenant-id` 只作为租户上下文,不能作为身份依据。 - - 生产 `.env` 必须通过 `readiness:production`:短信 provider 只允许阿里云/腾讯云,禁止 mock/未知值,禁止弱密钥、`CORS=*` 和 legacy 身份头。生产数据库还必须通过 `readiness:production:db`,校验租户短信/OAuth/支付 provider 的公开配置、HTTPS 回调、私密 `tenant_secrets` 和 RLS。 + - 生产 `.env` 必须通过 `readiness:production`:短信验证码 provider 使用 `AUTH_SMS_PROVIDER=aliyun-pnvs`,禁止 mock/未知值,禁止弱密钥、`CORS=*` 和 legacy 身份头。生产数据库还必须通过 `readiness:production:db` 和 `npm run diagnose:aliyun-pnvs`,校验租户 PNVS/OAuth/支付 provider 的公开配置、HTTPS 回调、私密 `tenant_secrets` 和 RLS。 2. 对象存储 - 已接阿里云 OSS、腾讯云 COS、Supabase Storage 的上传/下载签名 provider。 @@ -109,7 +109,7 @@ - 租户自有商户收款和平台代收/服务商模式。 2. 国内登录和短信 - - 已完成阿里云 PNVS 短信认证后端接入、远程短信登录 smoke 脚本、传统阿里云短信/腾讯云短信兼容 adapter 和本地 fake endpoint 测试。 + - 已完成阿里云 PNVS 短信认证后端接入、只读配置诊断脚本、远程短信登录 smoke 脚本、传统阿里云短信/腾讯云短信兼容 adapter 和本地 fake endpoint 测试;生产验证码登录按 PNVS 验收,传统 provider 不作为当前上线方案。 - 已完成微信小程序 `code2Session` 登录主链路。 - 已完成手机号绑定/换绑、微信网页登录、QQ 登录基础 API;继续补真实生产账号、回调域名和开放平台联调。 - 本地阶段不等待真实密钥,继续用 mock/fake provider 验证验证码、账号合并、登录审计、session 签发和错误处理;真实密钥、合法域名和开放平台错误码等上云后再联调。 diff --git a/docs/refactor/web-launch-acceptance-checklist.md b/docs/refactor/web-launch-acceptance-checklist.md index e7fcb94c..98f62193 100644 --- a/docs/refactor/web-launch-acceptance-checklist.md +++ b/docs/refactor/web-launch-acceptance-checklist.md @@ -130,7 +130,7 @@ PNVS 只验收手机号登录/换绑验证码链路。催缴、营销、CRM 等 ALLOW_LEGACY_AUTH_HEADERS=false ALLOW_PLATFORM_ADMIN_KEY=false CORS_ORIGIN=https://student.example.com,https://tenant-admin.example.com,https://platform-admin.example.com -AUTH_SMS_PROVIDER=aliyun-pnvs、aliyun 或 tencent +AUTH_SMS_PROVIDER=aliyun-pnvs ``` 生产 worker 推荐: diff --git a/scripts/aliyun-pnvs-provider-contract-test.js b/scripts/aliyun-pnvs-provider-contract-test.js index 024a8625..f4774d17 100644 --- a/scripts/aliyun-pnvs-provider-contract-test.js +++ b/scripts/aliyun-pnvs-provider-contract-test.js @@ -9,6 +9,7 @@ const deployEnvExample = fs.readFileSync(path.join(process.cwd(), 'scripts/deplo const providerDoc = fs.readFileSync(path.join(process.cwd(), 'docs/refactor/auth-payment-provider-plan.md'), 'utf8'); const launchChecklist = fs.readFileSync(path.join(process.cwd(), 'docs/refactor/web-launch-acceptance-checklist.md'), 'utf8'); const readinessSource = fs.readFileSync(path.join(process.cwd(), 'scripts/production-readiness-check.js'), 'utf8'); +const packageJson = fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'); const tenantProviderConfigSource = fs.readFileSync(path.join(process.cwd(), 'apps/api/src/core/tenant-provider-config.ts'), 'utf8'); const platformDunningNotificationWorker = fs.readFileSync(path.join(process.cwd(), 'apps/worker/src/jobs/platform-dunning-notifications.ts'), 'utf8'); @@ -30,6 +31,8 @@ assert.match(providerDoc, /SendSmsVerifyCode/, 'provider doc should document PNV assert.match(providerDoc, /CheckSmsVerifyCode/, 'provider doc should document PNVS verify action'); assert.match(providerDoc, /PNVS 只用于手机号登录和 `bind_phone`/, 'provider doc should constrain PNVS to verification-code auth flows'); assert.doesNotMatch(platformDunningNotificationWorker, /aliyun-pnvs|SendSmsVerifyCode|CheckSmsVerifyCode/, 'platform dunning notifications must not use PNVS verification APIs'); -assert.match(launchChecklist, /AUTH_SMS_PROVIDER=aliyun-pnvs/, 'launch checklist should allow aliyun-pnvs'); +assert.match(packageJson, /diagnose:aliyun-pnvs/, 'package scripts should expose PNVS diagnostics'); +assert.match(launchChecklist, /AUTH_SMS_PROVIDER=aliyun-pnvs\s*```/, 'launch checklist should require aliyun-pnvs for production SMS verification'); +assert.match(launchChecklist, /diagnose:aliyun-pnvs/, 'launch checklist should run PNVS diagnostics before remote smoke'); console.log('[PASS] Aliyun PNVS provider contract'); diff --git a/scripts/deploy/env/api.env.example b/scripts/deploy/env/api.env.example index c0ccfccd..47916051 100644 --- a/scripts/deploy/env/api.env.example +++ b/scripts/deploy/env/api.env.example @@ -18,7 +18,7 @@ AUTH_SESSION_SECRET=replace-with-strong-random-session-secret AUTH_CODE_PEPPER=replace-with-strong-random-code-pepper PLATFORM_ADMIN_API_KEY=replace-with-strong-random-platform-admin-key -# Supported production values: aliyun, aliyun-pnvs, tencent. +# Production SMS verification uses aliyun-pnvs. Traditional aliyun/tencent adapters are compatibility paths only. # Tenant-level SMS AccessKey/SecretKey live in app_private.tenant_secrets, not in this env file. AUTH_SMS_PROVIDER=aliyun-pnvs