const http = require("http"); const https = require("https"); const crypto = require("crypto"); const { loadWebhookSubs } = require("./store"); // Generate HMAC-SHA256 signature for webhook payloads function webhookSignature(secret, payload) { return 'sha256=' + crypto.createHmac('sha256', secret).update(payload).digest('hex'); } // Deliver webhook with exponential backoff (max 5 retries) async function deliverWebhook(sub, payload, attempt) { attempt = attempt || 1; const payloadStr = JSON.stringify(payload); const sig = webhookSignature(sub.secret, payloadStr); return new Promise((resolve) => { try { const parsed = new URL(sub.endpoint_url); const lib = parsed.protocol === 'https:' ? https : http; const options = { hostname: parsed.hostname, port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80), path: parsed.pathname + parsed.search, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payloadStr), 'X-PeerCortex-Signature': sig, 'X-PeerCortex-Event': 'hijack_detected', 'User-Agent': 'PeerCortex-Webhook/1.0' }, timeout: 10000 }; const req = lib.request(options, (res) => { let body = ''; res.on('data', c => body += c); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve({ ok: true, status: res.statusCode }); } else if (attempt < 5) { const delay = Math.pow(2, attempt) * 1000; setTimeout(() => deliverWebhook(sub, payload, attempt + 1).then(resolve), delay); } else { resolve({ ok: false, status: res.statusCode, error: 'max_retries' }); } }); }); req.on('error', () => { if (attempt < 5) { const delay = Math.pow(2, attempt) * 1000; setTimeout(() => deliverWebhook(sub, payload, attempt + 1).then(resolve), delay); } else { resolve({ ok: false, error: 'connection_failed' }); } }); req.on('timeout', () => { req.destroy(); }); req.write(payloadStr); req.end(); } catch(e) { resolve({ ok: false, error: e.message }); } }); } // Fire webhooks for all subscribers of an ASN when hijack detected async function notifyWebhooks(asn, alert) { const subs = loadWebhookSubs().filter(s => String(s.asn) === String(asn) && s.active !== false); for (const sub of subs) { const payload = { event: 'hijack_detected', asn: asn, alert: alert, timestamp: new Date().toISOString(), source: 'PeerCortex BGP Monitor' }; deliverWebhook(sub, payload, 1).then(result => { if (!result.ok) console.warn(`[WEBHOOK] Delivery failed for AS${asn} → ${sub.endpoint_url}: ${result.error || result.status}`); }); } } module.exports = { webhookSignature, deliverWebhook, notifyWebhooks };