diff --git a/apps/api/src/core/config.ts b/apps/api/src/core/config.ts index ff56ae33..39ead072 100644 --- a/apps/api/src/core/config.ts +++ b/apps/api/src/core/config.ts @@ -54,16 +54,11 @@ const DEFAULT_MAX_JSON_BODY_BYTES = 1024 * 1024; const DEFAULT_MAX_IMPORT_JSON_BODY_BYTES = 10 * 1024 * 1024; const HARD_MAX_JSON_BODY_BYTES = 50 * 1024 * 1024; const PRODUCTION_SMS_PROVIDERS = new Set([ - 'aliyun', - 'aliyun-sms', - 'aliyun_sms', 'aliyun-pnvs', 'aliyun_pnvs', + 'aliyun-pnvs-sms', 'aliyun-sms-auth', 'aliyun_sms_auth', - 'tencent', - 'tencent-sms', - 'tencent_sms', ]); const PRODUCTION_STORAGE_PROVIDERS = new Set(['aliyun_oss', 'tencent_cos', 'supabase_storage']); @@ -113,7 +108,7 @@ function validateProductionConfig(nextConfig: ApiConfig) { const failures: string[] = []; if (nextConfig.corsOrigins.includes('*')) failures.push('CORS_ORIGIN must not include * in production'); if (!PRODUCTION_SMS_PROVIDERS.has(nextConfig.authSmsProvider.trim().toLowerCase())) { - failures.push('AUTH_SMS_PROVIDER must be aliyun/aliyun-sms, aliyun-pnvs, or tencent/tencent-sms in production'); + failures.push('AUTH_SMS_PROVIDER must be aliyun-pnvs in production'); } if (isUnsafeSecret(nextConfig.authCodePepper, DEFAULT_AUTH_CODE_PEPPER)) { failures.push('AUTH_CODE_PEPPER must be a strong production secret'); diff --git a/scripts/production-config-failfast-test.js b/scripts/production-config-failfast-test.js index c4917174..585ebcd9 100644 --- a/scripts/production-config-failfast-test.js +++ b/scripts/production-config-failfast-test.js @@ -27,7 +27,7 @@ const safeBaseEnv = { const safeApiEnv = { ...safeBaseEnv, CORS_ORIGIN: 'https://student.gongxue100.com,https://tenant-admin.gongxue100.com,https://platform-admin.gongxue100.com', - AUTH_SMS_PROVIDER: 'aliyun', + AUTH_SMS_PROVIDER: 'aliyun-pnvs', AUTH_CODE_PEPPER: 's3cure-prod-code-pepper-2026-06-30-abcdef', AUTH_SESSION_SECRET: 's3cure-prod-session-secret-2026-06-30-ghijkl', AUTH_JWT_JWKS_URL: 'https://auth.gongxue100.com/auth/v1/.well-known/jwks.json', @@ -69,8 +69,19 @@ const unsafeApiSmsProvider = runImport(apiConfigUrl, { assert.notEqual(unsafeApiSmsProvider.status, 0, 'production API config should reject unsupported SMS provider'); assert.match( unsafeApiSmsProvider.output, - /AUTH_SMS_PROVIDER must be aliyun\/aliyun-sms, aliyun-pnvs, or tencent\/tencent-sms/, - 'API config should name supported production SMS providers', + /AUTH_SMS_PROVIDER must be aliyun-pnvs in production/, + 'API config should require PNVS for production SMS', +); + +const unsafeApiTraditionalSmsProvider = runImport(apiConfigUrl, { + ...safeApiEnv, + AUTH_SMS_PROVIDER: 'aliyun', +}); +assert.notEqual(unsafeApiTraditionalSmsProvider.status, 0, 'production API config should reject traditional Aliyun SMS provider'); +assert.match( + unsafeApiTraditionalSmsProvider.output, + /AUTH_SMS_PROVIDER must be aliyun-pnvs in production/, + 'API config should reject non-PNVS SMS providers in production', ); const unsafeApiStoragePublicBaseUrl = runImport(apiConfigUrl, { diff --git a/scripts/production-readiness-check-test.js b/scripts/production-readiness-check-test.js index f8214175..a82c8b2f 100644 --- a/scripts/production-readiness-check-test.js +++ b/scripts/production-readiness-check-test.js @@ -84,6 +84,37 @@ assert.ok( 'readiness should block unsupported AUTH_SMS_PROVIDER values', ); +const traditionalAliyunSmsProvider = runReadiness(` +NODE_ENV=production +DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku +CORS_ORIGIN=https://student.gongxue100.com +AUTH_SMS_PROVIDER=aliyun +AUTH_CODE_PEPPER=s3cure-prod-code-pepper-2026-06-29-abcdef +AUTH_SESSION_SECRET=s3cure-prod-session-secret-2026-06-29-ghijkl +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=s3cure-platform-admin-key-2026-06-29-mnopqr +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 +`); + +assert.notEqual(traditionalAliyunSmsProvider.status, 0, 'traditional Aliyun SMS provider readiness should fail in production'); +assert.ok( + traditionalAliyunSmsProvider.payload.checks?.some(item => item.id === 'env.auth_sms_provider' && item.status === 'blocker'), + 'readiness should require PNVS instead of traditional Aliyun SMS', +); + const strongSecretA = 's3cure-prod-code-pepper-2026-06-29-abcdef'; const strongSecretB = 's3cure-prod-session-secret-2026-06-29-ghijkl'; const strongSecretC = 's3cure-platform-admin-key-2026-06-29-mnopqr'; @@ -92,7 +123,7 @@ const safe = 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 +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 @@ -269,7 +300,7 @@ const unsafeProviderFixture = 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 +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 @@ -346,7 +377,7 @@ const missingJwksIssuer = runReadiness(` NODE_ENV=production DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku CORS_ORIGIN=https://student.gongxue100.com -AUTH_SMS_PROVIDER=aliyun +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 @@ -378,7 +409,7 @@ const unsafePlatformAuditNotificationLocalhost = runReadiness(` NODE_ENV=production DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku CORS_ORIGIN=https://student.gongxue100.com -AUTH_SMS_PROVIDER=aliyun +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 @@ -410,7 +441,7 @@ const unsafePlatformDunningNotificationLocalhost = runReadiness(` NODE_ENV=production DATABASE_URL=postgresql://prod_user:prod_password@db.prod.internal:5432/tiku CORS_ORIGIN=https://student.gongxue100.com -AUTH_SMS_PROVIDER=aliyun +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 diff --git a/scripts/production-readiness-check.js b/scripts/production-readiness-check.js index d9ae8c13..6ccb5edf 100644 --- a/scripts/production-readiness-check.js +++ b/scripts/production-readiness-check.js @@ -11,16 +11,11 @@ const DEFAULT_AUTH_JWT_SECRET = 'development-jwt-secret-change-me'; const DEFAULT_PLATFORM_ADMIN_API_KEY = 'local-platform-admin-key'; const HARD_MAX_JSON_BODY_BYTES = 50 * 1024 * 1024; const PRODUCTION_SMS_PROVIDERS = new Set([ - 'aliyun', - 'aliyun-sms', - 'aliyun_sms', 'aliyun-pnvs', 'aliyun_pnvs', + 'aliyun-pnvs-sms', 'aliyun-sms-auth', 'aliyun_sms_auth', - 'tencent', - 'tencent-sms', - 'tencent_sms', ]); const PRODUCTION_STORAGE_PROVIDERS = new Set(['aliyun_oss', 'tencent_cos', 'supabase_storage']); const AUTH_PROVIDER_ALIASES = { @@ -174,8 +169,6 @@ function providerIn(provider, aliases) { 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]) }; } @@ -576,11 +569,11 @@ function validateEnv() { const authSmsProvider = env('AUTH_SMS_PROVIDER', 'mock').trim().toLowerCase(); if (!PRODUCTION_SMS_PROVIDERS.has(authSmsProvider)) { - block('env.auth_sms_provider', 'AUTH_SMS_PROVIDER must be aliyun/aliyun-sms, aliyun-pnvs, or tencent/tencent-sms in production', { + block('env.auth_sms_provider', 'AUTH_SMS_PROVIDER must be aliyun-pnvs in production', { provider: authSmsProvider || '(empty)', }); } else { - pass('env.auth_sms_provider', 'AUTH_SMS_PROVIDER is a supported production SMS provider', { provider: authSmsProvider }); + pass('env.auth_sms_provider', 'AUTH_SMS_PROVIDER is aliyun-pnvs for production SMS authentication', { provider: authSmsProvider }); } if (isUnsafeSecret(env('AUTH_CODE_PEPPER', DEFAULT_AUTH_CODE_PEPPER), DEFAULT_AUTH_CODE_PEPPER)) {