fix: reject legacy SMS auth providers in production

This commit is contained in:
Codex
2026-07-04 00:39:56 +08:00
parent c71768fdb0
commit ca482f7feb
2 changed files with 13 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
import { randomBytes } from 'node:crypto'; import { randomBytes } from 'node:crypto';
import type pg from 'pg'; import type pg from 'pg';
import { HttpError, type RequestContext } from '../../core/http.js'; import { HttpError, type RequestContext } from '../../core/http.js';
import { config as appConfig } from '../../core/config.js';
import { intParam, optionalString, readJsonBody, requiredString, stringParam } from '../../core/request.js'; import { intParam, optionalString, readJsonBody, requiredString, stringParam } from '../../core/request.js';
import { query, queryOne, transaction } from '../../core/db.js'; import { query, queryOne, transaction } from '../../core/db.js';
import { import {
@@ -390,6 +391,14 @@ function defaultAuthSecretScope(provider: string): SecretScope {
return 'oauth'; return 'oauth';
} }
function assertProductionAuthProviderAllowed(provider: string, status: string) {
if (!appConfig.isProduction || !['active', 'testing'].includes(status)) return;
const normalized = provider.toLowerCase().replace(/[_\s]/g, '-');
if (normalized === 'aliyun' || normalized === 'aliyun-sms' || normalized === 'tencent' || normalized === 'tencent-sms') {
throw new HttpError(400, 'Production SMS auth providers must use aliyun-pnvs', 'PRODUCTION_SMS_PROVIDER_MUST_BE_PNVS');
}
}
function parseSecretScope(value: unknown, fallback: SecretScope): SecretScope { function parseSecretScope(value: unknown, fallback: SecretScope): SecretScope {
const candidate = (nullableString(value) || fallback) as SecretScope; const candidate = (nullableString(value) || fallback) as SecretScope;
if (!SECRET_SCOPES.has(candidate)) { if (!SECRET_SCOPES.has(candidate)) {
@@ -1400,6 +1409,8 @@ export async function upsertAuthProviderRoute(ctx: RequestContext) {
const fallbackScope = defaultAuthSecretScope(provider); const fallbackScope = defaultAuthSecretScope(provider);
const secretPayload = parseSecretPayload(body, fallbackScope, provider, provider); const secretPayload = parseSecretPayload(body, fallbackScope, provider, provider);
const configPublic = objectValue(body.configPublic); const configPublic = objectValue(body.configPublic);
const status = optionalStatus(body.status, AUTH_STATUSES, 'disabled');
assertProductionAuthProviderAllowed(provider, status);
const item = await transaction(async client => { const item = await transaction(async client => {
const secret = secretPayload ? await upsertTenantSecret(client, auth, secretPayload) : null; const secret = secretPayload ? await upsertTenantSecret(client, auth, secretPayload) : null;
@@ -1420,7 +1431,7 @@ export async function upsertAuthProviderRoute(ctx: RequestContext) {
[ [
auth.tenantId, auth.tenantId,
provider, provider,
optionalStatus(body.status, AUTH_STATUSES, 'disabled'), status,
optionalString(body, 'displayName') || null, optionalString(body, 'displayName') || null,
publicJsonValue(configPublic), publicJsonValue(configPublic),
], ],

View File

@@ -26,6 +26,7 @@ assert.match(routeSource, /export async function bindPhoneRoute[\s\S]*activeSmsP
assert.match(routeSource, /purpose !== 'bind_phone'/, 'phone binding should require bind_phone purpose'); assert.match(routeSource, /purpose !== 'bind_phone'/, 'phone binding should require bind_phone purpose');
assert.match(tenantProviderConfigSource, /normalized === 'aliyun-pnvs'[\s\S]*return 'sms'/, 'PNVS secrets should default to sms scope'); assert.match(tenantProviderConfigSource, /normalized === 'aliyun-pnvs'[\s\S]*return 'sms'/, 'PNVS secrets should default to sms scope');
assert.match(tenantAdminRouteSource, /normalized === 'aliyun-pnvs'[\s\S]*return 'sms'/, 'tenant admin PNVS secrets should default to sms scope'); assert.match(tenantAdminRouteSource, /normalized === 'aliyun-pnvs'[\s\S]*return 'sms'/, 'tenant admin PNVS secrets should default to sms scope');
assert.match(tenantAdminRouteSource, /assertProductionAuthProviderAllowed[\s\S]*PRODUCTION_SMS_PROVIDER_MUST_BE_PNVS/, 'tenant admin should reject active legacy SMS auth providers in production');
assert.match(deployEnvExample, /AUTH_SMS_PROVIDER=aliyun-pnvs/, 'deploy env example should prefer aliyun-pnvs'); assert.match(deployEnvExample, /AUTH_SMS_PROVIDER=aliyun-pnvs/, 'deploy env example should prefer aliyun-pnvs');
assert.match(readinessSource, /aliyunPnvs/, 'production readiness should validate aliyun-pnvs public config'); assert.match(readinessSource, /aliyunPnvs/, 'production readiness should validate aliyun-pnvs public config');
assert.match(providerDoc, /SendSmsVerifyCode/, 'provider doc should document PNVS send action'); assert.match(providerDoc, /SendSmsVerifyCode/, 'provider doc should document PNVS send action');