feat: add platform dunning notifications

This commit is contained in:
Codex
2026-06-30 07:28:32 +08:00
parent e66623158d
commit 51f8cbdec8
26 changed files with 1782 additions and 24 deletions

View File

@@ -333,6 +333,12 @@ function validateEnv() {
pass('env.worker_platform_audit_notification_insecure_localhost', 'Platform audit notification worker insecure localhost webhook mode is disabled');
}
if (envBool('WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST', false)) {
block('env.worker_platform_dunning_notification_insecure_localhost', 'WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST must be false in production');
} else {
pass('env.worker_platform_dunning_notification_insecure_localhost', 'Platform dunning notification worker insecure localhost webhook mode is disabled');
}
const requiredPositiveNumbers = [
'WORKER_CRM_BATCH_SIZE',
'WORKER_COMMERCE_BATCH_SIZE',
@@ -470,6 +476,51 @@ async function validateDatabase() {
pass('db.platform_audit_notification_secrets', 'Signed platform audit notification channels have private secret rows');
}
const unsafePlatformDunningNotificationRows = await pool.query(`
select id, channel_code, provider, webhook_url
from public.platform_dunning_notification_channels
where enabled = true
and (
webhook_url !~* '^https://'
or webhook_url ~* '^https?://(localhost|127\\.0\\.0\\.1|\\[?::1\\]?)'
)
`);
if (unsafePlatformDunningNotificationRows.rowCount > 0) {
block('db.platform_dunning_notification_webhooks', 'Enabled platform dunning notification webhooks must use production HTTPS URLs', {
count: unsafePlatformDunningNotificationRows.rowCount,
samples: unsafePlatformDunningNotificationRows.rows.slice(0, 5).map(row => ({
id: row.id,
channelCode: row.channel_code,
provider: row.provider,
})),
});
} else {
pass('db.platform_dunning_notification_webhooks', 'Enabled platform dunning notification webhooks use production HTTPS URLs');
}
const missingPlatformDunningNotificationSecretRows = await pool.query(`
select c.id, c.channel_code, c.provider, c.secret_ref
from public.platform_dunning_notification_channels c
left join app_private.platform_secrets s
on s.secret_scope = split_part(c.secret_ref, ':', 2)
and s.secret_key = split_part(c.secret_ref, ':', 3)
where c.enabled = true
and c.provider in ('dingtalk', 'feishu')
and (c.secret_ref is null or c.secret_ref !~ '^app_private\\.platform_secrets:' or s.id is null)
`);
if (missingPlatformDunningNotificationSecretRows.rowCount > 0) {
block('db.platform_dunning_notification_secrets', 'Signed platform dunning notification channels require app_private.platform_secrets rows', {
count: missingPlatformDunningNotificationSecretRows.rowCount,
samples: missingPlatformDunningNotificationSecretRows.rows.slice(0, 5).map(row => ({
id: row.id,
channelCode: row.channel_code,
provider: row.provider,
})),
});
} else {
pass('db.platform_dunning_notification_secrets', 'Signed platform dunning notification channels have private secret rows');
}
const unverifiedDomainRows = await pool.query(`
select count(*)::int as count
from public.tenant_domains