@hookflo/tern
Version:
A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms
213 lines (212 loc) • 6.59 kB
JavaScript
;
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;
}