UNPKG

@hookflo/tern

Version:

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

202 lines (201 loc) โ€ข 9.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runTests = runTests; const crypto_1 = require("crypto"); const index_1 = require("./index"); const testSecret = 'whsec_test_secret_key_12345'; const testBody = JSON.stringify({ event: 'test', data: { id: '123' } }); function createMockRequest(headers, body = testBody) { return new Request('https://example.com/webhook', { method: 'POST', headers, body, }); } function createStripeSignature(body, secret, timestamp) { const signedPayload = `${timestamp}.${body}`; const hmac = (0, crypto_1.createHmac)('sha256', secret); hmac.update(signedPayload); const signature = hmac.digest('hex'); return `t=${timestamp},v1=${signature}`; } function createGitHubSignature(body, secret) { const hmac = (0, crypto_1.createHmac)('sha256', secret); hmac.update(body); return `sha256=${hmac.digest('hex')}`; } function createClerkSignature(body, secret, id, timestamp) { const signedContent = `${id}.${timestamp}.${body}`; const secretBytes = new Uint8Array(Buffer.from(secret.split('_')[1], 'base64')); const hmac = (0, crypto_1.createHmac)('sha256', secretBytes); hmac.update(signedContent); return `v1,${hmac.digest('base64')}`; } async function runTests() { console.log('๐Ÿงช Running Webhook Verification Tests...\n'); // Test 1: Stripe Webhook console.log('1. Testing Stripe Webhook...'); try { const timestamp = Math.floor(Date.now() / 1000); const stripeSignature = createStripeSignature(testBody, testSecret, timestamp); const stripeRequest = createMockRequest({ 'stripe-signature': stripeSignature, 'content-type': 'application/json', }); const stripeResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(stripeRequest, 'stripe', testSecret); console.log(' โœ… Stripe:', stripeResult.isValid ? 'PASSED' : 'FAILED'); if (!stripeResult.isValid) { console.log(' โŒ Error:', stripeResult.error); } } catch (error) { console.log(' โŒ Stripe test failed:', error); } // Test 2: GitHub Webhook console.log('\n2. Testing GitHub Webhook...'); try { const githubSignature = createGitHubSignature(testBody, testSecret); const githubRequest = createMockRequest({ 'x-hub-signature-256': githubSignature, 'x-github-event': 'push', 'x-github-delivery': 'test-delivery-id', 'content-type': 'application/json', }); const githubResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(githubRequest, 'github', testSecret); console.log(' โœ… GitHub:', githubResult.isValid ? 'PASSED' : 'FAILED'); if (!githubResult.isValid) { console.log(' โŒ Error:', githubResult.error); } } catch (error) { console.log(' โŒ GitHub test failed:', error); } // Test 3: Clerk Webhook console.log('\n3. Testing Clerk Webhook...'); try { // Create a proper Clerk-style secret (whsec_ + base64 encoded secret) const base64Secret = Buffer.from(testSecret).toString('base64'); const clerkSecret = `whsec_${base64Secret}`; const id = 'test-id-123'; const timestamp = Math.floor(Date.now() / 1000); const clerkSignature = createClerkSignature(testBody, clerkSecret, id, timestamp); const clerkRequest = createMockRequest({ 'svix-signature': clerkSignature, 'svix-id': id, 'svix-timestamp': timestamp.toString(), 'content-type': 'application/json', }); const clerkResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(clerkRequest, 'clerk', clerkSecret); console.log(' โœ… Clerk:', clerkResult.isValid ? 'PASSED' : 'FAILED'); if (!clerkResult.isValid) { console.log(' โŒ Error:', clerkResult.error); } } catch (error) { console.log(' โŒ Clerk test failed:', error); } // Test 4: Standard HMAC-SHA256 (Generic) console.log('\n4. Testing Standard HMAC-SHA256...'); try { const hmac = (0, crypto_1.createHmac)('sha256', testSecret); hmac.update(testBody); const signature = hmac.digest('hex'); const genericRequest = createMockRequest({ 'x-webhook-signature': signature, 'content-type': 'application/json', }); const genericResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(genericRequest, 'unknown', testSecret); console.log(' โœ… Generic HMAC-SHA256:', genericResult.isValid ? 'PASSED' : 'FAILED'); if (!genericResult.isValid) { console.log(' โŒ Error:', genericResult.error); } } catch (error) { console.log(' โŒ Generic test failed:', error); } // Test 4.5: Dodo Payments (Standard Webhooks / svix-style) console.log('\n4.5. Testing Dodo Payments...'); try { const webhookId = 'test-webhook-id-123'; const timestamp = Math.floor(Date.now() / 1000); // Create a proper secret format for Standard Webhooks (whsec_ + base64 encoded secret) const base64Secret = Buffer.from(testSecret).toString('base64'); const dodoSecret = `whsec_${base64Secret}`; // Create svix-style signature: {webhook-id}.{webhook-timestamp}.{payload} const signedContent = `${webhookId}.${timestamp}.${testBody}`; // Use the base64-decoded secret for HMAC (like the Standard Webhooks library) const secretBytes = new Uint8Array(Buffer.from(base64Secret, 'base64')); const hmac = (0, crypto_1.createHmac)('sha256', secretBytes); hmac.update(signedContent); const signature = `v1,${hmac.digest('base64')}`; const dodoRequest = createMockRequest({ 'webhook-signature': signature, 'webhook-id': webhookId, 'webhook-timestamp': timestamp.toString(), 'content-type': 'application/json', }); const dodoResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(dodoRequest, 'dodopayments', dodoSecret); console.log(' โœ… Dodo Payments:', dodoResult.isValid ? 'PASSED' : 'FAILED'); if (!dodoResult.isValid) { console.log(' โŒ Error:', dodoResult.error); } } catch (error) { console.log(' โŒ Dodo Payments test failed:', error); } // Test 5: Token-based (Supabase) console.log('\n5. Testing Token-based Authentication...'); try { const webhookId = 'test-webhook-id'; const webhookToken = 'test-webhook-token'; const tokenRequest = createMockRequest({ 'x-webhook-id': webhookId, 'x-webhook-token': webhookToken, 'content-type': 'application/json', }); const tokenResult = await index_1.WebhookVerificationService.verifyTokenBased(tokenRequest, webhookId, webhookToken); console.log(' โœ… Token-based:', tokenResult.isValid ? 'PASSED' : 'FAILED'); if (!tokenResult.isValid) { console.log(' โŒ Error:', tokenResult.error); } } catch (error) { console.log(' โŒ Token-based test failed:', error); } // Test 6: Invalid signatures console.log('\n6. Testing Invalid Signatures...'); try { const invalidRequest = createMockRequest({ 'stripe-signature': 't=1234567890,v1=invalid_signature', 'content-type': 'application/json', }); const invalidResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(invalidRequest, 'stripe', testSecret); console.log(' โœ… Invalid signature correctly rejected:', !invalidResult.isValid ? 'PASSED' : 'FAILED'); if (invalidResult.isValid) { console.log(' โŒ Should have been rejected'); } } catch (error) { console.log(' โŒ Invalid signature test failed:', error); } // Test 7: Missing headers console.log('\n7. Testing Missing Headers...'); try { const missingHeaderRequest = createMockRequest({ 'content-type': 'application/json', }); const missingHeaderResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(missingHeaderRequest, 'stripe', testSecret); console.log(' โœ… Missing headers correctly rejected:', !missingHeaderResult.isValid ? 'PASSED' : 'FAILED'); if (missingHeaderResult.isValid) { console.log(' โŒ Should have been rejected'); } } catch (error) { console.log(' โŒ Missing headers test failed:', error); } console.log('\n๐ŸŽ‰ All tests completed!'); } // Run tests if this file is executed directly if (require.main === module) { runTests().catch(console.error); }