← back to Costa Rica
lib/preflight.js
73 lines
'use strict';
// TK-10346 — boot-time fail-closed guard against a silent revenue-loss misconfig.
//
// If a payment/WhatsApp integration is LIVE but its webhook secret is missing,
// verifyWebhook returns { ok: false } for EVERY real webhook -> the charge succeeds
// at the processor but our booking never transitions to confirmed. Payments happen,
// bookings don't, and nothing errors loudly. This catches it at boot.
//
// In production this THROWS (a live money system that cannot verify webhooks must not
// serve — fail closed). In dev/sandbox it only warns. It is INERT when nothing is live,
// so it can never affect the current sandbox prod.
//
// SCOPE: this guards the webhook-secret / verify-token misconfig ONLY. It does NOT prove
// the live charge path is correct — the go-live memo's LIVE-ONLY preflight (createCharge
// providerRef=undefined guard, base64-vs-hex signature encoding) must still be checked
// against the real provider. "Boot passed" != "safe to open bookings".
const SECRET_ENV = { tilopay: 'TILOPAY_WEBHOOK_SECRET', onvo: 'ONVO_WEBHOOK_SECRET' };
// Pure — returns an array of human-readable problem strings (empty = all good).
function checkWebhookSecrets({ getProvider, whatsapp } = {}) {
const problems = [];
if (getProvider) {
try {
const pay = getProvider();
if (pay && pay.liveMode && !pay.webhookSecretSet) {
const envName = SECRET_ENV[pay.name] || `${String(pay.name).toUpperCase()}_WEBHOOK_SECRET`;
problems.push(
`payment provider "${pay.name}" is LIVE but its webhook secret is missing ` +
`(set ${envName}) — real payment webhooks would be silently rejected and bookings never confirm.`
);
}
} catch (e) {
problems.push(`payment provider preflight could not resolve: ${e && e.message}`);
}
}
if (whatsapp && whatsapp.liveMode) {
if (!whatsapp.webhookSecretSet) {
problems.push(
`WhatsApp is LIVE but WHATSAPP_APP_SECRET is missing — inbound webhooks would be silently rejected.`
);
}
if (!whatsapp.verifyTokenSet) {
problems.push(
`WhatsApp is LIVE but WHATSAPP_VERIFY_TOKEN is missing or left at the public default ` +
`('cr-verify-sandbox') — set a real random token before registering the Meta webhook.`
);
}
}
return problems;
}
// Side-effecting boot gate. Logs a loud banner; throws in production.
function runPreflight(deps = {}) {
const env = deps.env || process.env;
const problems = checkWebhookSecrets(deps);
if (problems.length) {
const line = '═'.repeat(79);
console.error(
'\n' + line +
'\nFATAL PREFLIGHT — live integration without webhook verification:\n' +
problems.map((p) => ' • ' + p).join('\n') +
'\n' + line + '\n'
);
if ((env.NODE_ENV || '') === 'production') {
throw new Error('preflight failed: ' + problems.join(' | '));
}
}
return problems;
}
module.exports = { checkWebhookSecrets, runPreflight };