Files
gongxue-base/scripts/configure-aliyun-pnvs-provider.js

190 lines
6.7 KiB
JavaScript

import { fileURLToPath, pathToFileURL } from 'node:url';
import pg from 'pg';
const DEFAULT_TENANT_ID = '00000000-0000-0000-0000-000000000001';
function envString(env, key, fallback = '') {
return typeof env[key] === 'string' && env[key].trim() ? env[key].trim() : fallback;
}
function envNumberString(env, key, fallback) {
const value = envString(env, key, fallback);
return /^\d+$/.test(value) ? value : fallback;
}
function envJsonObject(env, key, fallback) {
const raw = envString(env, key);
if (!raw) return fallback;
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`${key} must be a JSON object`);
}
return parsed;
}
function buildConfig(env = process.env) {
const databaseUrl = envString(env, 'DATABASE_URL');
const tenantId = envString(env, 'PNVS_TENANT_ID', envString(env, 'TENANT_ID', DEFAULT_TENANT_ID));
const accessKeyId = envString(env, 'ALIYUN_ACCESS_KEY_ID');
const accessKeySecret = envString(env, 'ALIYUN_ACCESS_KEY_SECRET');
const signName = envString(env, 'ALIYUN_PNVS_SIGN_NAME', envString(env, 'ALIYUN_SIGN_NAME'));
const templateCode = envString(env, 'ALIYUN_PNVS_TEMPLATE_CODE', envString(env, 'ALIYUN_TEMPLATE_CODE'));
const missing = [];
if (!databaseUrl) missing.push('DATABASE_URL');
if (!tenantId) missing.push('PNVS_TENANT_ID or TENANT_ID');
if (!accessKeyId) missing.push('ALIYUN_ACCESS_KEY_ID');
if (!accessKeySecret) missing.push('ALIYUN_ACCESS_KEY_SECRET');
if (!signName) missing.push('ALIYUN_PNVS_SIGN_NAME or ALIYUN_SIGN_NAME');
if (!templateCode) missing.push('ALIYUN_PNVS_TEMPLATE_CODE or ALIYUN_TEMPLATE_CODE');
if (missing.length > 0) throw new Error(`Missing required PNVS config env: ${missing.join(', ')}`);
const validTime = envNumberString(env, 'ALIYUN_PNVS_VALID_TIME', '300');
const interval = envNumberString(env, 'ALIYUN_PNVS_INTERVAL', '60');
const templateParam = envJsonObject(env, 'ALIYUN_PNVS_TEMPLATE_PARAM', {
code: '##code##',
min: String(Math.max(1, Math.ceil(Number(validTime) / 60))),
});
if (!Object.values(templateParam).some(value => String(value) === '##code##')) {
templateParam.code = '##code##';
}
return {
databaseUrl,
tenantId,
accessKeyId,
accessKeySecret,
signName,
templateCode,
displayName: envString(env, 'ALIYUN_PNVS_DISPLAY_NAME', '阿里云短信认证'),
endpoint: envString(env, 'ALIYUN_PNVS_ENDPOINT', 'https://dypnsapi.aliyuncs.com'),
regionId: envString(env, 'ALIYUN_PNVS_REGION_ID', 'cn-hangzhou'),
countryCode: envString(env, 'ALIYUN_PNVS_COUNTRY_CODE', '86'),
codeType: envNumberString(env, 'ALIYUN_PNVS_CODE_TYPE', '1'),
codeLength: envNumberString(env, 'ALIYUN_PNVS_CODE_LENGTH', '6'),
validTime,
interval,
duplicatePolicy: envNumberString(env, 'ALIYUN_PNVS_DUPLICATE_POLICY', '1'),
schemeName: envString(env, 'ALIYUN_PNVS_SCHEME_NAME'),
templateParam,
};
}
function publicConfig(config) {
return {
signName: config.signName,
templateCode: config.templateCode,
endpoint: config.endpoint,
regionId: config.regionId,
countryCode: config.countryCode,
templateParam: config.templateParam,
codeType: config.codeType,
codeLength: config.codeLength,
validTime: config.validTime,
interval: config.interval,
duplicatePolicy: config.duplicatePolicy,
...(config.schemeName ? { schemeName: config.schemeName } : {}),
secretRef: 'app_private.tenant_secrets:sms:aliyun-pnvs',
};
}
async function configureAliyunPnvsProvider(inputConfig, options = {}) {
const config = inputConfig?.databaseUrl ? inputConfig : buildConfig(options.env || process.env);
const configPublic = publicConfig(config);
if (options.dryRun) {
return {
tenantId: config.tenantId,
provider: 'aliyun-pnvs',
configPublic,
secretScope: 'sms',
secretKey: 'aliyun-pnvs',
};
}
const pool = new pg.Pool({ connectionString: config.databaseUrl, max: 1 });
try {
await pool.query('begin');
await pool.query(
`
insert into app_private.tenant_secrets (
tenant_id, secret_scope, secret_key, provider, secret_json, last_rotated_at
)
values ($1::uuid, 'sms', 'aliyun-pnvs', 'aliyun-pnvs', $2::jsonb, now())
on conflict (tenant_id, secret_scope, secret_key)
do update set provider = excluded.provider,
secret_json = excluded.secret_json,
last_rotated_at = now(),
updated_at = now()
`,
[
config.tenantId,
JSON.stringify({
accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret,
}),
],
);
const providerResult = await pool.query(
`
insert into public.tenant_auth_providers (
tenant_id, provider, status, display_name, config_public
)
values ($1::uuid, 'aliyun-pnvs', 'active', $2, $3::jsonb)
on conflict (tenant_id, provider)
do update set status = excluded.status,
display_name = excluded.display_name,
config_public = excluded.config_public,
updated_at = now()
returning tenant_id as "tenantId", provider, status, config_public as "configPublic"
`,
[config.tenantId, config.displayName, JSON.stringify(configPublic)],
);
await pool.query('commit');
return providerResult.rows[0];
} catch (error) {
await pool.query('rollback').catch(() => undefined);
throw error;
} finally {
await pool.end();
}
}
async function main() {
try {
const dryRun = process.argv.includes('--dry-run');
const result = await configureAliyunPnvsProvider(null, { dryRun });
console.log(JSON.stringify({
ok: true,
dryRun,
tenantId: result.tenantId,
provider: result.provider,
status: result.status || 'active',
secretRef: result.configPublic?.secretRef || 'app_private.tenant_secrets:sms:aliyun-pnvs',
}, null, 2));
} catch (error) {
console.error(error.message);
console.error(`
Required example:
DATABASE_URL=postgresql://tiku_app:***@127.0.0.1:54322/postgres
PNVS_TENANT_ID=00000000-0000-0000-0000-000000000001
ALIYUN_ACCESS_KEY_ID=<real-access-key-id>
ALIYUN_ACCESS_KEY_SECRET=<real-access-key-secret>
ALIYUN_PNVS_SIGN_NAME=<sms-sign-name>
ALIYUN_PNVS_TEMPLATE_CODE=<template-code>
Optional:
ALIYUN_PNVS_TEMPLATE_PARAM='{"code":"##code##","min":"5"}'
ALIYUN_PNVS_VALID_TIME=300
ALIYUN_PNVS_INTERVAL=60
`);
process.exitCode = 1;
}
}
const currentFile = fileURLToPath(import.meta.url);
if (process.argv[1] && fileURLToPath(pathToFileURL(process.argv[1])) === currentFile) {
await main();
}
export { buildConfig, configureAliyunPnvsProvider, publicConfig };