UNPKG

@hookflo/tern

Version:

A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms

213 lines (212 loc) 6.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getRecommendedAlgorithm = getRecommendedAlgorithm; exports.platformSupportsAlgorithm = platformSupportsAlgorithm; exports.getPlatformsByAlgorithm = getPlatformsByAlgorithm; exports.createSignatureConfigForPlatform = createSignatureConfigForPlatform; exports.isValidSignatureConfig = isValidSignatureConfig; exports.getPlatformDescription = getPlatformDescription; exports.isCustomAlgorithm = isCustomAlgorithm; exports.getAlgorithmStats = getAlgorithmStats; exports.getMostCommonAlgorithm = getMostCommonAlgorithm; exports.detectPlatformFromHeaders = detectPlatformFromHeaders; exports.getPlatformSummary = getPlatformSummary; exports.compareSignatureConfigs = compareSignatureConfigs; exports.cloneSignatureConfig = cloneSignatureConfig; exports.mergeSignatureConfigs = mergeSignatureConfigs; exports.validatePlatformConfig = validatePlatformConfig; exports.getValidPlatforms = getValidPlatforms; exports.getPlatformsByAlgorithmType = getPlatformsByAlgorithmType; exports.cleanHeaders = cleanHeaders; const types_1 = require("./types"); const algorithms_1 = require("./platforms/algorithms"); /** * Utility functions for the scalable webhook verification framework */ /** * Get the recommended algorithm for a platform */ function getRecommendedAlgorithm(platform) { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); return config.signatureConfig.algorithm; } /** * Check if a platform supports a specific algorithm */ function platformSupportsAlgorithm(platform, algorithm) { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); return config.signatureConfig.algorithm === algorithm; } /** * Get all platforms that support a specific algorithm */ function getPlatformsByAlgorithm(algorithm) { const { getPlatformsUsingAlgorithm } = require('./platforms/algorithms'); return getPlatformsUsingAlgorithm(algorithm); } /** * Create a signature config for a platform */ function createSignatureConfigForPlatform(platform) { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); return config.signatureConfig; } /** * Validate a signature config */ function isValidSignatureConfig(config) { return (0, algorithms_1.validateSignatureConfig)(config); } /** * Get platform description */ function getPlatformDescription(platform) { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); return config.description || `Webhook verification for ${platform}`; } /** * Check if a platform uses custom algorithm */ function isCustomAlgorithm(platform) { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); return config.signatureConfig.algorithm === 'custom'; } /** * Get algorithm statistics */ function getAlgorithmStats() { const platforms = Object.values(types_1.WebhookPlatformKeys); const stats = {}; for (const platform of platforms) { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); const { algorithm } = config.signatureConfig; stats[algorithm] = (stats[algorithm] || 0) + 1; } return stats; } /** * Get most common algorithm */ function getMostCommonAlgorithm() { const stats = getAlgorithmStats(); return Object.entries(stats).reduce((a, b) => (stats[a[0]] > stats[b[0]] ? a : b))[0]; } /** * Check if a request matches a platform's signature pattern */ function detectPlatformFromHeaders(headers) { const headerMap = new Map(); headers.forEach((value, key) => { headerMap.set(key.toLowerCase(), value); }); // GitHub if (headerMap.has('x-hub-signature-256')) { return 'github'; } // Stripe if (headerMap.has('stripe-signature')) { return 'stripe'; } // Clerk if (headerMap.has('svix-signature')) { return 'clerk'; } // Dodo Payments if (headerMap.has('webhook-signature')) { return 'dodopayments'; } // Shopify if (headerMap.has('x-shopify-hmac-sha256')) { return 'shopify'; } // Vercel if (headerMap.has('x-vercel-signature')) { return 'vercel'; } // Polar if (headerMap.has('x-polar-signature')) { return 'polar'; } // Supabase if (headerMap.has('x-webhook-token')) { return 'supabase'; } return null; } /** * Get platform configuration summary */ function getPlatformSummary() { const platforms = Object.values(types_1.WebhookPlatformKeys); return platforms.map((platform) => { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); return { platform, algorithm: config.signatureConfig.algorithm, description: config.description || '', isCustom: config.signatureConfig.algorithm === 'custom', }; }); } /** * Compare two signature configs */ function compareSignatureConfigs(config1, config2) { return JSON.stringify(config1) === JSON.stringify(config2); } /** * Clone a signature config */ function cloneSignatureConfig(config) { return JSON.parse(JSON.stringify(config)); } /** * Merge signature configs (config2 overrides config1) */ function mergeSignatureConfigs(config1, config2) { return { ...config1, ...config2 }; } /** * Validate platform configuration */ function validatePlatformConfig(platform) { try { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); return (0, algorithms_1.validateSignatureConfig)(config.signatureConfig); } catch { return false; } } /** * Get all valid platforms */ function getValidPlatforms() { const platforms = Object.values(types_1.WebhookPlatformKeys); return platforms.filter((platform) => validatePlatformConfig(platform)); } /** * Get platforms by algorithm type */ function getPlatformsByAlgorithmType() { const platforms = Object.values(types_1.WebhookPlatformKeys); const result = {}; for (const platform of platforms) { const config = (0, algorithms_1.getPlatformAlgorithmConfig)(platform); const { algorithm } = config.signatureConfig; if (!result[algorithm]) { result[algorithm] = []; } result[algorithm].push(platform); } return result; } function cleanHeaders(headers) { const cleaned = {}; for (const [key, value] of Object.entries(headers)) { if (value !== undefined) { cleaned[key] = value; } } return cleaned; }