forked from wangziqi/gongxue-base
215 lines
9.1 KiB
JavaScript
215 lines
9.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const repoRoot = process.cwd();
|
|
const apiConfigUrl = pathToFileURL(`${repoRoot}/apps/api/src/core/config.ts`).href;
|
|
const workerConfigUrl = pathToFileURL(`${repoRoot}/apps/worker/src/config.ts`).href;
|
|
|
|
const safeBaseEnv = {
|
|
NODE_ENV: 'production',
|
|
DATABASE_URL: 'postgresql://prod_user:prod_password@db.prod.internal:5432/tiku',
|
|
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_PRODUCTION_CONFIG_TEST_ONLY',
|
|
ALIYUN_OSS_ACCESS_KEY_SECRET: 'aliyun-production-config-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-30-abcdef',
|
|
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN: 'false',
|
|
WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST: 'false',
|
|
WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST: 'false',
|
|
};
|
|
|
|
const safeApiEnv = {
|
|
...safeBaseEnv,
|
|
CORS_ORIGIN: 'https://student.gongxue100.com,https://tenant-admin.gongxue100.com,https://platform-admin.gongxue100.com',
|
|
CORS_TENANT_DOMAINS_ENABLED: 'true',
|
|
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',
|
|
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-30-mnopqr',
|
|
};
|
|
|
|
function runImport(moduleUrl, env) {
|
|
const result = spawnSync(process.execPath, ['--import', 'tsx', '-e', `await import(${JSON.stringify(moduleUrl)})`], {
|
|
cwd: repoRoot,
|
|
encoding: 'utf8',
|
|
env: {
|
|
PATH: process.env.PATH || '',
|
|
Path: process.env.Path || '',
|
|
SystemRoot: process.env.SystemRoot || '',
|
|
ComSpec: process.env.ComSpec || '',
|
|
TEMP: process.env.TEMP || '',
|
|
TMP: process.env.TMP || '',
|
|
...env,
|
|
},
|
|
});
|
|
return { ...result, output: `${result.stdout || ''}${result.stderr || ''}` };
|
|
}
|
|
|
|
const unsafeApi = runImport(apiConfigUrl, {
|
|
...safeApiEnv,
|
|
STORAGE_DEFAULT_PROVIDER: 'local_dev',
|
|
});
|
|
assert.notEqual(unsafeApi.status, 0, 'production API config should reject local_dev storage');
|
|
assert.match(unsafeApi.output, /Invalid production API configuration/, 'API config should explain production config failure');
|
|
assert.match(unsafeApi.output, /STORAGE_DEFAULT_PROVIDER=local_dev/, 'API config should name unsafe storage provider');
|
|
|
|
const unsafeApiSmsProvider = runImport(apiConfigUrl, {
|
|
...safeApiEnv,
|
|
AUTH_SMS_PROVIDER: 'mocked-production',
|
|
});
|
|
assert.notEqual(unsafeApiSmsProvider.status, 0, 'production API config should reject unsupported SMS provider');
|
|
assert.match(
|
|
unsafeApiSmsProvider.output,
|
|
/AUTH_SMS_PROVIDER must be aliyun-pnvs in production/,
|
|
'API config should require PNVS for production SMS',
|
|
);
|
|
|
|
const unsafeApiTenantCorsDisabled = runImport(apiConfigUrl, {
|
|
...safeApiEnv,
|
|
CORS_TENANT_DOMAINS_ENABLED: 'false',
|
|
});
|
|
assert.notEqual(unsafeApiTenantCorsDisabled.status, 0, 'production API config should require dynamic tenant CORS');
|
|
assert.match(
|
|
unsafeApiTenantCorsDisabled.output,
|
|
/CORS_TENANT_DOMAINS_ENABLED must be true in production/,
|
|
'API config should fail closed when dynamic tenant CORS is disabled',
|
|
);
|
|
|
|
const unsafeApiCorsPath = runImport(apiConfigUrl, {
|
|
...safeApiEnv,
|
|
CORS_ORIGIN: 'https://platform-admin.gongxue100.com/app',
|
|
});
|
|
assert.notEqual(unsafeApiCorsPath.status, 0, 'production API config should reject non-Origin CORS URLs');
|
|
assert.match(
|
|
unsafeApiCorsPath.output,
|
|
/CORS_ORIGIN must contain only production HTTPS origins without paths/,
|
|
'API config should reject CORS entries with URL paths',
|
|
);
|
|
|
|
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, {
|
|
...safeApiEnv,
|
|
STORAGE_PUBLIC_BASE_URL: 'http://127.0.0.1:9000/assets',
|
|
});
|
|
assert.notEqual(unsafeApiStoragePublicBaseUrl.status, 0, 'production API config should reject unsafe storage public base URL');
|
|
assert.match(
|
|
unsafeApiStoragePublicBaseUrl.output,
|
|
/STORAGE_PUBLIC_BASE_URL must be a production HTTPS URL/,
|
|
'API config should require HTTPS storage public base URL',
|
|
);
|
|
|
|
const unsafeApiAliyunInternal = runImport(apiConfigUrl, {
|
|
...safeApiEnv,
|
|
ALIYUN_OSS_INTERNAL: 'true',
|
|
});
|
|
assert.notEqual(unsafeApiAliyunInternal.status, 0, 'production API config should reject internal OSS signed URL mode');
|
|
assert.match(
|
|
unsafeApiAliyunInternal.output,
|
|
/ALIYUN_OSS_INTERNAL=true is not allowed/,
|
|
'API config should name unsafe internal OSS mode',
|
|
);
|
|
|
|
const unsafeApiAliyunEndpoint = runImport(apiConfigUrl, {
|
|
...safeApiEnv,
|
|
ALIYUN_OSS_ENDPOINT: 'https://oss-cn-hangzhou.example.com',
|
|
});
|
|
assert.notEqual(unsafeApiAliyunEndpoint.status, 0, 'production API config should reject non-Aliyun OSS endpoints');
|
|
assert.match(
|
|
unsafeApiAliyunEndpoint.output,
|
|
/ALIYUN_OSS_ENDPOINT must be a production HTTPS aliyuncs\.com endpoint/,
|
|
'API config should require official Aliyun OSS endpoint host',
|
|
);
|
|
|
|
const safeApi = runImport(apiConfigUrl, safeApiEnv);
|
|
assert.equal(safeApi.status, 0, `safe production API config should load: ${safeApi.output}`);
|
|
|
|
const safeApiUnderscorePnvsAlias = runImport(apiConfigUrl, {
|
|
...safeApiEnv,
|
|
AUTH_SMS_PROVIDER: 'aliyun_pnvs',
|
|
});
|
|
assert.equal(safeApiUnderscorePnvsAlias.status, 0, `underscore PNVS alias should load in production API config: ${safeApiUnderscorePnvsAlias.output}`);
|
|
|
|
const unsafeWorker = runImport(workerConfigUrl, {
|
|
...safeBaseEnv,
|
|
STORAGE_DEFAULT_PROVIDER: 'local_dev',
|
|
});
|
|
assert.notEqual(unsafeWorker.status, 0, 'production worker config should reject local_dev storage');
|
|
assert.match(unsafeWorker.output, /Invalid production worker configuration/, 'worker config should explain production config failure');
|
|
assert.match(unsafeWorker.output, /STORAGE_DEFAULT_PROVIDER=local_dev/, 'worker config should name unsafe storage provider');
|
|
|
|
const unsafeWorkerScanner = runImport(workerConfigUrl, {
|
|
...safeBaseEnv,
|
|
WORKER_ASSET_SECURITY_SCANNER: 'metadata_rules',
|
|
});
|
|
assert.notEqual(unsafeWorkerScanner.status, 0, 'production worker config should require external scanner');
|
|
assert.match(unsafeWorkerScanner.output, /WORKER_ASSET_SECURITY_SCANNER must include http/, 'worker config should require http scanner');
|
|
|
|
const unsafeWorkerStorageBaseUrl = runImport(workerConfigUrl, {
|
|
...safeBaseEnv,
|
|
STORAGE_PUBLIC_BASE_URL: 'http://localhost:9000/assets',
|
|
});
|
|
assert.notEqual(unsafeWorkerStorageBaseUrl.status, 0, 'production worker config should reject unsafe storage public base URL');
|
|
assert.match(
|
|
unsafeWorkerStorageBaseUrl.output,
|
|
/STORAGE_PUBLIC_BASE_URL must be a production HTTPS URL/,
|
|
'worker config should require HTTPS storage public base URL',
|
|
);
|
|
|
|
const unsafeWorkerAliyunInternal = runImport(workerConfigUrl, {
|
|
...safeBaseEnv,
|
|
ALIYUN_OSS_INTERNAL: 'true',
|
|
});
|
|
assert.notEqual(unsafeWorkerAliyunInternal.status, 0, 'production worker config should reject internal OSS signed URL mode');
|
|
assert.match(
|
|
unsafeWorkerAliyunInternal.output,
|
|
/ALIYUN_OSS_INTERNAL=true is not allowed/,
|
|
'worker config should name unsafe internal OSS mode',
|
|
);
|
|
|
|
const unsafeWorkerPlatformAuditNotification = runImport(workerConfigUrl, {
|
|
...safeBaseEnv,
|
|
WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST: 'true',
|
|
});
|
|
assert.notEqual(unsafeWorkerPlatformAuditNotification.status, 0, 'production worker config should reject platform audit notification localhost mode');
|
|
assert.match(
|
|
unsafeWorkerPlatformAuditNotification.output,
|
|
/WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=true/,
|
|
'worker config should name unsafe platform audit notification localhost mode',
|
|
);
|
|
|
|
const unsafeWorkerPlatformDunningNotification = runImport(workerConfigUrl, {
|
|
...safeBaseEnv,
|
|
WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST: 'true',
|
|
});
|
|
assert.notEqual(unsafeWorkerPlatformDunningNotification.status, 0, 'production worker config should reject platform dunning notification localhost mode');
|
|
assert.match(
|
|
unsafeWorkerPlatformDunningNotification.output,
|
|
/WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=true/,
|
|
'worker config should name unsafe platform dunning notification localhost mode',
|
|
);
|
|
|
|
const safeWorker = runImport(workerConfigUrl, safeBaseEnv);
|
|
assert.equal(safeWorker.status, 0, `safe production worker config should load: ${safeWorker.output}`);
|
|
|
|
console.log('[PASS] production config fail-fast');
|