diff --git a/scripts/production-readiness-check-test.js b/scripts/production-readiness-check-test.js index 1b6b87fc..f8214175 100644 --- a/scripts/production-readiness-check-test.js +++ b/scripts/production-readiness-check-test.js @@ -212,6 +212,57 @@ assert.ok( pnvsTemplateParamWarning.payload.checks?.some(item => item.id === 'db.auth.aliyun-pnvs.template_param' && item.status === 'warn'), 'readiness should warn when PNVS templateParam lacks ##code##', ); +assert.ok( + pnvsTemplateParamWarning.payload.checks?.some(item => item.id === 'db.auth_sms_provider_configured' && item.status === 'pass'), + 'readiness should pass when AUTH_SMS_PROVIDER has a matching PNVS provider row', +); + +const mismatchedSmsProviderFixture = runReadiness( + ` +NODE_ENV=production +DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku +CORS_ORIGIN=https://student.gongxue100.com,https://tenant-admin.gongxue100.com,https://platform-admin.gongxue100.com +AUTH_SMS_PROVIDER=aliyun-pnvs +AUTH_CODE_PEPPER=${strongSecretA} +AUTH_SESSION_SECRET=${strongSecretB} +AUTH_JWT_JWKS_URL=https://auth.gongxue100.com/auth/v1/.well-known/jwks.json +AUTH_JWT_ISSUER=https://auth.gongxue100.com/auth/v1 +ALLOW_LEGACY_AUTH_HEADERS=false +ALLOW_PLATFORM_ADMIN_KEY=false +PLATFORM_ADMIN_API_KEY=${strongSecretC} +STORAGE_DEFAULT_PROVIDER=aliyun_oss +STORAGE_DEFAULT_BUCKET=tiku-assets +STORAGE_REQUIRE_TENANT_PREFIX=true +ALIYUN_OSS_REGION=cn-hangzhou +ALIYUN_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com +ALIYUN_OSS_ACCESS_KEY_ID=LTAI_READINESS_TEST_ONLY +ALIYUN_OSS_ACCESS_KEY_SECRET=aliyun-readiness-secret-placeholder +WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http +WORKER_ASSET_SECURITY_SCAN_HTTP_ENDPOINT=https://scanner.gongxue100.com/api/scan +WORKER_ASSET_SECURITY_SCAN_HTTP_TOKEN=s3cure-asset-scanner-token-2026-06-29-stuvwx +WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false +`, + { + providerRows: [ + { + source: 'auth', + tenantId: 'tenant-aliyun', + provider: 'aliyun', + configPublic: { + signName: '短信签名', + templateCode: 'SMS_123456789', + endpoint: 'https://dysmsapi.aliyuncs.com', + }, + }, + ], + }, +); + +assert.notEqual(mismatchedSmsProviderFixture.status, 0, 'readiness should fail when AUTH_SMS_PROVIDER has no matching active provider row'); +assert.ok( + mismatchedSmsProviderFixture.payload.checks?.some(item => item.id === 'db.auth_sms_provider_configured' && item.status === 'blocker'), + 'readiness should block env/provider mismatch', +); const unsafeProviderFixture = runReadiness( ` diff --git a/scripts/production-readiness-check.js b/scripts/production-readiness-check.js index 90047d65..d9ae8c13 100644 --- a/scripts/production-readiness-check.js +++ b/scripts/production-readiness-check.js @@ -171,6 +171,32 @@ function providerIn(provider, aliases) { return aliases.has(normalizeProvider(provider)); } +function currentSmsProviderAliases() { + const provider = normalizeProvider(env('AUTH_SMS_PROVIDER', 'mock')).replace(/_/g, '-'); + if (AUTH_PROVIDER_ALIASES.aliyunPnvs.has(provider)) return { provider: 'aliyun-pnvs', aliases: AUTH_PROVIDER_ALIASES.aliyunPnvs }; + if (AUTH_PROVIDER_ALIASES.aliyun.has(provider)) return { provider: 'aliyun', aliases: AUTH_PROVIDER_ALIASES.aliyun }; + if (AUTH_PROVIDER_ALIASES.tencent.has(provider)) return { provider: 'tencent', aliases: AUTH_PROVIDER_ALIASES.tencent }; + return { provider, aliases: new Set([provider]) }; +} + +function validateActiveSmsProviderRows(rows, sourceLabel = 'database') { + const expected = currentSmsProviderAliases(); + if (!PRODUCTION_SMS_PROVIDERS.has(normalizeProvider(env('AUTH_SMS_PROVIDER', 'mock')))) return; + const activeRows = rows.filter(row => row.source === 'auth' && providerIn(row.provider, expected.aliases)); + if (activeRows.length === 0) { + block('db.auth_sms_provider_configured', 'AUTH_SMS_PROVIDER must have a matching active/testing tenant_auth_providers row', { + provider: expected.provider, + source: sourceLabel, + }); + return; + } + pass('db.auth_sms_provider_configured', 'AUTH_SMS_PROVIDER has matching active/testing tenant auth provider rows', { + provider: expected.provider, + count: activeRows.length, + source: sourceLabel, + }); +} + function validateProviderUrl({ id, value, @@ -457,6 +483,7 @@ function validateProviderConfigRows(inputRows) { warn('db.provider_fixture.empty', 'Provider config fixture did not contain any rows'); return; } + validateActiveSmsProviderRows(rows, 'fixture'); for (const row of rows) { const secretPaths = findSecretLikePaths(row.config_public || {}); if (secretPaths.length > 0) { @@ -793,6 +820,7 @@ async function validateDatabase() { if (publicSecretRows.rows.every(row => findSecretLikePaths(row.config_public || {}).length === 0)) { pass('db.provider_public_config', 'Active provider public configs do not contain secret-like keys'); } + validateActiveSmsProviderRows(publicSecretRows.rows, 'database'); const activePlatformAdminRows = await pool.query(` select id, username, phone, auth_user_id