@hookflo/tern 4.0.2 โ 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/express.d.ts +1 -0
- package/dist/adapters/express.js +10 -1
- package/dist/adapters/shared.d.ts +1 -0
- package/dist/adapters/shared.js +10 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +77 -65
- package/dist/upstash/queue.js +81 -28
- package/dist/verifiers/algorithms.d.ts +1 -0
- package/dist/verifiers/algorithms.js +58 -13
- package/package.json +2 -2
- package/dist/adapters/testExpress.d.ts +0 -1
- package/dist/adapters/testExpress.js +0 -83
- package/dist/test-compiled.d.ts +0 -1
- package/dist/test-compiled.js +0 -4
- package/dist/test.d.ts +0 -2
- package/dist/test.js +0 -668
package/dist/test.js
DELETED
|
@@ -1,668 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runTests = runTests;
|
|
4
|
-
const crypto_1 = require("crypto");
|
|
5
|
-
const index_1 = require("./index");
|
|
6
|
-
const testSecret = 'whsec_test_secret_key_12345';
|
|
7
|
-
const testBody = JSON.stringify({ event: 'test', data: { id: '123' } });
|
|
8
|
-
function createMockRequest(headers, body = testBody) {
|
|
9
|
-
return new Request('https://example.com/webhook', {
|
|
10
|
-
method: 'POST',
|
|
11
|
-
headers,
|
|
12
|
-
body,
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
function createStripeSignature(body, secret, timestamp) {
|
|
16
|
-
const signedPayload = `${timestamp}.${body}`;
|
|
17
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
18
|
-
hmac.update(signedPayload);
|
|
19
|
-
const signature = hmac.digest('hex');
|
|
20
|
-
return `t=${timestamp},v1=${signature}`;
|
|
21
|
-
}
|
|
22
|
-
function createGitHubSignature(body, secret) {
|
|
23
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
24
|
-
hmac.update(body);
|
|
25
|
-
return `sha256=${hmac.digest('hex')}`;
|
|
26
|
-
}
|
|
27
|
-
function createGitLabSignature(body, secret) {
|
|
28
|
-
// GitLab just compares the token in X-Gitlab-Token header
|
|
29
|
-
return secret;
|
|
30
|
-
}
|
|
31
|
-
function createClerkSignature(body, secret, id, timestamp) {
|
|
32
|
-
const signedContent = `${id}.${timestamp}.${body}`;
|
|
33
|
-
const secretBytes = new Uint8Array(Buffer.from(secret.split('_')[1], 'base64'));
|
|
34
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secretBytes);
|
|
35
|
-
hmac.update(signedContent);
|
|
36
|
-
return `v1,${hmac.digest('base64')}`;
|
|
37
|
-
}
|
|
38
|
-
function createStandardWebhooksSignature(body, secret, id, timestamp) {
|
|
39
|
-
const signedContent = `${id}.${timestamp}.${body}`;
|
|
40
|
-
const base64Secret = secret.includes('_') ? secret.split('_')[1] : secret;
|
|
41
|
-
const secretBytes = new Uint8Array(Buffer.from(base64Secret, 'base64'));
|
|
42
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secretBytes);
|
|
43
|
-
hmac.update(signedContent);
|
|
44
|
-
return `v1,${hmac.digest('base64')}`;
|
|
45
|
-
}
|
|
46
|
-
function createPaddleSignature(body, secret, timestamp) {
|
|
47
|
-
const signedPayload = `${timestamp}:${body}`;
|
|
48
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
49
|
-
hmac.update(signedPayload);
|
|
50
|
-
return `ts=${timestamp};h1=${hmac.digest('hex')}`;
|
|
51
|
-
}
|
|
52
|
-
function createShopifySignature(body, secret) {
|
|
53
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
54
|
-
hmac.update(body);
|
|
55
|
-
return hmac.digest('base64');
|
|
56
|
-
}
|
|
57
|
-
function createWooCommerceSignature(body, secret) {
|
|
58
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
59
|
-
hmac.update(body);
|
|
60
|
-
return hmac.digest('base64');
|
|
61
|
-
}
|
|
62
|
-
function createWorkOSSignature(body, secret, timestamp) {
|
|
63
|
-
const signedPayload = `${timestamp}.${body}`;
|
|
64
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
65
|
-
hmac.update(signedPayload);
|
|
66
|
-
return `t=${timestamp},v1=${hmac.digest('hex')}`;
|
|
67
|
-
}
|
|
68
|
-
function createSentrySignature(body, secret) {
|
|
69
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
70
|
-
hmac.update(JSON.stringify(JSON.parse(body)));
|
|
71
|
-
return hmac.digest('hex');
|
|
72
|
-
}
|
|
73
|
-
function createSentryIssueAlertSignature(issueAlertObject, secret) {
|
|
74
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
75
|
-
hmac.update(JSON.stringify(issueAlertObject));
|
|
76
|
-
return hmac.digest('hex');
|
|
77
|
-
}
|
|
78
|
-
function createGrafanaSignature(body, secret, timestamp) {
|
|
79
|
-
const payload = timestamp ? `${timestamp}.${body}` : body;
|
|
80
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
81
|
-
hmac.update(payload);
|
|
82
|
-
return hmac.digest('hex');
|
|
83
|
-
}
|
|
84
|
-
function createDopplerSignature(body, secret) {
|
|
85
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
86
|
-
hmac.update(body);
|
|
87
|
-
return `sha256=${hmac.digest('hex')}`;
|
|
88
|
-
}
|
|
89
|
-
function createSanitySignature(body, secret, timestamp) {
|
|
90
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secret);
|
|
91
|
-
hmac.update(`${timestamp}.${body}`);
|
|
92
|
-
return `t=${timestamp},v1=${hmac.digest('hex')}`;
|
|
93
|
-
}
|
|
94
|
-
function createFalPayloadToSign(body, requestId, userId, timestamp) {
|
|
95
|
-
const bodyHash = (0, crypto_1.createHash)('sha256').update(body).digest('hex');
|
|
96
|
-
return `${requestId}\n${userId}\n${timestamp}\n${bodyHash}`;
|
|
97
|
-
}
|
|
98
|
-
async function runTests() {
|
|
99
|
-
console.log('๐งช Running Webhook Verification Tests...\n');
|
|
100
|
-
// Test 1: Stripe Webhook
|
|
101
|
-
console.log('1. Testing Stripe Webhook...');
|
|
102
|
-
try {
|
|
103
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
104
|
-
const stripeSignature = createStripeSignature(testBody, testSecret, timestamp);
|
|
105
|
-
const stripeRequest = createMockRequest({
|
|
106
|
-
'stripe-signature': stripeSignature,
|
|
107
|
-
'content-type': 'application/json',
|
|
108
|
-
});
|
|
109
|
-
const stripeResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(stripeRequest, 'stripe', testSecret);
|
|
110
|
-
const stripePassed = stripeResult.isValid && Boolean(stripeResult.eventId?.startsWith('stripe:'));
|
|
111
|
-
console.log(' โ
Stripe:', stripePassed ? 'PASSED' : 'FAILED');
|
|
112
|
-
if (!stripeResult.isValid) {
|
|
113
|
-
console.log(' โ Error:', stripeResult.error);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
console.log(' โ Stripe test failed:', error);
|
|
118
|
-
}
|
|
119
|
-
// Test 2: GitHub Webhook
|
|
120
|
-
console.log('\n2. Testing GitHub Webhook...');
|
|
121
|
-
try {
|
|
122
|
-
const githubSignature = createGitHubSignature(testBody, testSecret);
|
|
123
|
-
const githubRequest = createMockRequest({
|
|
124
|
-
'x-hub-signature-256': githubSignature,
|
|
125
|
-
'x-github-event': 'push',
|
|
126
|
-
'x-github-delivery': 'test-delivery-id',
|
|
127
|
-
'content-type': 'application/json',
|
|
128
|
-
});
|
|
129
|
-
const githubResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(githubRequest, 'github', testSecret);
|
|
130
|
-
console.log(' โ
GitHub:', githubResult.isValid ? 'PASSED' : 'FAILED');
|
|
131
|
-
if (!githubResult.isValid) {
|
|
132
|
-
console.log(' โ Error:', githubResult.error);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
console.log(' โ GitHub test failed:', error);
|
|
137
|
-
}
|
|
138
|
-
// Test 3: Clerk Webhook
|
|
139
|
-
console.log('\n3. Testing Clerk Webhook...');
|
|
140
|
-
try {
|
|
141
|
-
// Create a proper Clerk-style secret (whsec_ + base64 encoded secret)
|
|
142
|
-
const base64Secret = Buffer.from(testSecret).toString('base64');
|
|
143
|
-
const clerkSecret = `whsec_${base64Secret}`;
|
|
144
|
-
const id = 'test-id-123';
|
|
145
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
146
|
-
const clerkSignature = createClerkSignature(testBody, clerkSecret, id, timestamp);
|
|
147
|
-
const clerkRequest = createMockRequest({
|
|
148
|
-
'svix-signature': clerkSignature,
|
|
149
|
-
'svix-id': id,
|
|
150
|
-
'svix-timestamp': timestamp.toString(),
|
|
151
|
-
'content-type': 'application/json',
|
|
152
|
-
});
|
|
153
|
-
const clerkResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(clerkRequest, 'clerk', clerkSecret);
|
|
154
|
-
console.log(' โ
Clerk:', clerkResult.isValid ? 'PASSED' : 'FAILED');
|
|
155
|
-
if (!clerkResult.isValid) {
|
|
156
|
-
console.log(' โ Error:', clerkResult.error);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
console.log(' โ Clerk test failed:', error);
|
|
161
|
-
}
|
|
162
|
-
// Test 4: Standard HMAC-SHA256 (Generic)
|
|
163
|
-
console.log('\n4. Testing Standard HMAC-SHA256...');
|
|
164
|
-
try {
|
|
165
|
-
const hmac = (0, crypto_1.createHmac)('sha256', testSecret);
|
|
166
|
-
hmac.update(testBody);
|
|
167
|
-
const signature = hmac.digest('hex');
|
|
168
|
-
const genericRequest = createMockRequest({
|
|
169
|
-
'x-webhook-signature': signature,
|
|
170
|
-
'content-type': 'application/json',
|
|
171
|
-
});
|
|
172
|
-
const genericResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(genericRequest, 'unknown', testSecret);
|
|
173
|
-
console.log(' โ
Generic HMAC-SHA256:', genericResult.isValid ? 'PASSED' : 'FAILED');
|
|
174
|
-
if (!genericResult.isValid) {
|
|
175
|
-
console.log(' โ Error:', genericResult.error);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
console.log(' โ Generic test failed:', error);
|
|
180
|
-
}
|
|
181
|
-
// Test 4.5: Dodo Payments (Standard Webhooks / svix-style)
|
|
182
|
-
console.log('\n4.5. Testing Dodo Payments...');
|
|
183
|
-
try {
|
|
184
|
-
const webhookId = 'test-webhook-id-123';
|
|
185
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
186
|
-
// Create a proper secret format for Standard Webhooks (whsec_ + base64 encoded secret)
|
|
187
|
-
const base64Secret = Buffer.from(testSecret).toString('base64');
|
|
188
|
-
const dodoSecret = `whsec_${base64Secret}`;
|
|
189
|
-
// Create svix-style signature: {webhook-id}.{webhook-timestamp}.{payload}
|
|
190
|
-
const signedContent = `${webhookId}.${timestamp}.${testBody}`;
|
|
191
|
-
// Use the base64-decoded secret for HMAC (like the Standard Webhooks library)
|
|
192
|
-
const secretBytes = new Uint8Array(Buffer.from(base64Secret, 'base64'));
|
|
193
|
-
const hmac = (0, crypto_1.createHmac)('sha256', secretBytes);
|
|
194
|
-
hmac.update(signedContent);
|
|
195
|
-
const signature = `v1,${hmac.digest('base64')}`;
|
|
196
|
-
const dodoRequest = createMockRequest({
|
|
197
|
-
'webhook-signature': signature,
|
|
198
|
-
'webhook-id': webhookId,
|
|
199
|
-
'webhook-timestamp': timestamp.toString(),
|
|
200
|
-
'content-type': 'application/json',
|
|
201
|
-
});
|
|
202
|
-
const dodoResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(dodoRequest, 'dodopayments', dodoSecret);
|
|
203
|
-
console.log(' โ
Dodo Payments:', dodoResult.isValid ? 'PASSED' : 'FAILED');
|
|
204
|
-
if (!dodoResult.isValid) {
|
|
205
|
-
console.log(' โ Error:', dodoResult.error);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
catch (error) {
|
|
209
|
-
console.log(' โ Dodo Payments test failed:', error);
|
|
210
|
-
}
|
|
211
|
-
// Test 5: Token-based authentication helper
|
|
212
|
-
console.log('\n5. Testing Token-based Authentication...');
|
|
213
|
-
try {
|
|
214
|
-
const webhookId = 'test-webhook-id';
|
|
215
|
-
const webhookToken = 'test-webhook-token';
|
|
216
|
-
const tokenRequest = createMockRequest({
|
|
217
|
-
'x-webhook-id': webhookId,
|
|
218
|
-
'x-webhook-token': webhookToken,
|
|
219
|
-
'content-type': 'application/json',
|
|
220
|
-
});
|
|
221
|
-
const tokenResult = await index_1.WebhookVerificationService.verifyTokenAuth(tokenRequest.clone(), webhookId, webhookToken);
|
|
222
|
-
const tokenAliasResult = await index_1.WebhookVerificationService.verifyTokenBased(tokenRequest.clone(), webhookId, webhookToken);
|
|
223
|
-
const tokenPassed = tokenResult.isValid && tokenAliasResult.isValid;
|
|
224
|
-
console.log(' โ
Token-based:', tokenPassed ? 'PASSED' : 'FAILED');
|
|
225
|
-
if (!tokenResult.isValid) {
|
|
226
|
-
console.log(' โ Error:', tokenResult.error);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
console.log(' โ Token-based test failed:', error);
|
|
231
|
-
}
|
|
232
|
-
// Test 5.5: Sentry webhook verification (standard + issue alert fallback)
|
|
233
|
-
console.log('\n5.5. Testing Sentry Webhook...');
|
|
234
|
-
try {
|
|
235
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
236
|
-
const requestId = 'sentry-request-id-123';
|
|
237
|
-
const sentryBody = JSON.stringify({
|
|
238
|
-
action: 'triggered',
|
|
239
|
-
data: {
|
|
240
|
-
issue_alert: {
|
|
241
|
-
id: 'alert-1',
|
|
242
|
-
title: 'CPU high',
|
|
243
|
-
project: 'infra',
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
const sentryRequest = createMockRequest({
|
|
248
|
-
'sentry-hook-signature': createSentrySignature(sentryBody, testSecret),
|
|
249
|
-
'sentry-hook-timestamp': timestamp.toString(),
|
|
250
|
-
'request-id': requestId,
|
|
251
|
-
'content-type': 'application/json',
|
|
252
|
-
}, sentryBody);
|
|
253
|
-
const sentryResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(sentryRequest, 'sentry', testSecret);
|
|
254
|
-
const sentryIssueAlertRequest = createMockRequest({
|
|
255
|
-
'sentry-hook-signature': createSentryIssueAlertSignature({ id: 'alert-1', title: 'CPU high', project: 'infra' }, testSecret),
|
|
256
|
-
'sentry-hook-timestamp': timestamp.toString(),
|
|
257
|
-
'request-id': `${requestId}-issue-alert`,
|
|
258
|
-
'content-type': 'application/json',
|
|
259
|
-
}, sentryBody);
|
|
260
|
-
const sentryIssueAlertResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(sentryIssueAlertRequest, 'sentry', testSecret);
|
|
261
|
-
const sentryPassed = sentryResult.isValid && sentryIssueAlertResult.isValid && sentryResult.eventId?.startsWith('sentry:');
|
|
262
|
-
console.log(' โ
Sentry:', sentryPassed ? 'PASSED' : 'FAILED');
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
console.log(' โ Sentry test failed:', error);
|
|
266
|
-
}
|
|
267
|
-
// Test 5.6: Grafana webhook verification
|
|
268
|
-
console.log('\n5.6. Testing Grafana Webhook...');
|
|
269
|
-
try {
|
|
270
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
271
|
-
const grafanaSignature = createGrafanaSignature(testBody, testSecret, timestamp);
|
|
272
|
-
const grafanaRequest = createMockRequest({
|
|
273
|
-
'x-grafana-alerting-signature': grafanaSignature,
|
|
274
|
-
'x-grafana-alerting-timestamp': timestamp.toString(),
|
|
275
|
-
'content-type': 'application/json',
|
|
276
|
-
});
|
|
277
|
-
const grafanaResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(grafanaRequest, 'grafana', testSecret);
|
|
278
|
-
console.log(' โ
Grafana:', grafanaResult.isValid ? 'PASSED' : 'FAILED');
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
console.log(' โ Grafana test failed:', error);
|
|
282
|
-
}
|
|
283
|
-
// Test 5.7: Doppler webhook verification
|
|
284
|
-
console.log('\n5.7. Testing Doppler Webhook...');
|
|
285
|
-
try {
|
|
286
|
-
const dopplerRequest = createMockRequest({
|
|
287
|
-
'x-doppler-signature': createDopplerSignature(testBody, testSecret),
|
|
288
|
-
'content-type': 'application/json',
|
|
289
|
-
});
|
|
290
|
-
const dopplerResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(dopplerRequest, 'doppler', testSecret);
|
|
291
|
-
const dopplerPassed = dopplerResult.isValid && Boolean(dopplerResult.eventId?.startsWith('doppler:'));
|
|
292
|
-
console.log(' โ
Doppler:', dopplerPassed ? 'PASSED' : 'FAILED');
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
console.log(' โ Doppler test failed:', error);
|
|
296
|
-
}
|
|
297
|
-
// Test 5.8: Sanity webhook verification
|
|
298
|
-
console.log('\n5.8. Testing Sanity Webhook...');
|
|
299
|
-
try {
|
|
300
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
301
|
-
const idempotencyKey = 'sanity-idem-123';
|
|
302
|
-
const sanityRequest = createMockRequest({
|
|
303
|
-
'sanity-webhook-signature': createSanitySignature(testBody, testSecret, timestamp),
|
|
304
|
-
'idempotency-key': idempotencyKey,
|
|
305
|
-
'content-type': 'application/json',
|
|
306
|
-
});
|
|
307
|
-
const sanityResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(sanityRequest, 'sanity', testSecret);
|
|
308
|
-
const sanityPassed = sanityResult.isValid && sanityResult.eventId === `sanity:${idempotencyKey}`;
|
|
309
|
-
console.log(' โ
Sanity:', sanityPassed ? 'PASSED' : 'FAILED');
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
console.log(' โ Sanity test failed:', error);
|
|
313
|
-
}
|
|
314
|
-
// Test 6: Invalid signatures
|
|
315
|
-
console.log('\n6. Testing Invalid Signatures...');
|
|
316
|
-
try {
|
|
317
|
-
const invalidRequest = createMockRequest({
|
|
318
|
-
'stripe-signature': 't=1234567890,v1=invalid_signature',
|
|
319
|
-
'content-type': 'application/json',
|
|
320
|
-
});
|
|
321
|
-
const invalidResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(invalidRequest, 'stripe', testSecret);
|
|
322
|
-
const invalidSigPassed = !invalidResult.isValid && (invalidResult.errorCode === 'INVALID_SIGNATURE'
|
|
323
|
-
|| invalidResult.errorCode === 'TIMESTAMP_EXPIRED');
|
|
324
|
-
console.log(' โ
Invalid signature correctly rejected:', invalidSigPassed ? 'PASSED' : 'FAILED');
|
|
325
|
-
if (invalidResult.isValid) {
|
|
326
|
-
console.log(' โ Should have been rejected');
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
console.log(' โ Invalid signature test failed:', error);
|
|
331
|
-
}
|
|
332
|
-
// Test 7: Missing headers
|
|
333
|
-
console.log('\n7. Testing Missing Headers...');
|
|
334
|
-
try {
|
|
335
|
-
const missingHeaderRequest = createMockRequest({
|
|
336
|
-
'content-type': 'application/json',
|
|
337
|
-
});
|
|
338
|
-
const missingHeaderResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(missingHeaderRequest, 'stripe', testSecret);
|
|
339
|
-
const missingHeaderPassed = !missingHeaderResult.isValid && missingHeaderResult.errorCode === 'MISSING_SIGNATURE';
|
|
340
|
-
console.log(' โ
Missing headers correctly rejected:', missingHeaderPassed ? 'PASSED' : 'FAILED');
|
|
341
|
-
if (missingHeaderResult.isValid) {
|
|
342
|
-
console.log(' โ Should have been rejected');
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
console.log(' โ Missing headers test failed:', error);
|
|
347
|
-
}
|
|
348
|
-
// Test 8: GitLab Webhook
|
|
349
|
-
console.log('\n8. Testing GitLab Webhook...');
|
|
350
|
-
try {
|
|
351
|
-
const gitlabSecret = testSecret;
|
|
352
|
-
const gitlabRequest = createMockRequest({
|
|
353
|
-
'X-Gitlab-Token': gitlabSecret,
|
|
354
|
-
'content-type': 'application/json',
|
|
355
|
-
});
|
|
356
|
-
const gitlabResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(gitlabRequest, 'gitlab', gitlabSecret);
|
|
357
|
-
console.log(' โ
GitLab:', gitlabResult.isValid ? 'PASSED' : 'FAILED');
|
|
358
|
-
if (!gitlabResult.isValid) {
|
|
359
|
-
console.log(' โ Error:', gitlabResult.error);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
catch (error) {
|
|
363
|
-
console.log(' โ GitLab test failed:', error);
|
|
364
|
-
}
|
|
365
|
-
// Test 9: GitLab Invalid Token
|
|
366
|
-
console.log('\n9. Testing GitLab Invalid Token...');
|
|
367
|
-
try {
|
|
368
|
-
const gitlabRequest = createMockRequest({
|
|
369
|
-
'X-Gitlab-Token': 'wrong_secret',
|
|
370
|
-
'content-type': 'application/json',
|
|
371
|
-
});
|
|
372
|
-
const gitlabResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(gitlabRequest, 'gitlab', testSecret);
|
|
373
|
-
console.log(' โ
Invalid token correctly rejected:', !gitlabResult.isValid ? 'PASSED' : 'FAILED');
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
console.log(' โ GitLab invalid token test failed:', error);
|
|
377
|
-
}
|
|
378
|
-
// Test 10: verifyAny should auto-detect Stripe
|
|
379
|
-
console.log('\n10. Testing verifyAny auto-detection...');
|
|
380
|
-
try {
|
|
381
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
382
|
-
const stripeSignature = createStripeSignature(testBody, testSecret, timestamp);
|
|
383
|
-
const request = createMockRequest({
|
|
384
|
-
'stripe-signature': stripeSignature,
|
|
385
|
-
'content-type': 'application/json',
|
|
386
|
-
});
|
|
387
|
-
const result = await index_1.WebhookVerificationService.verifyAny(request, {
|
|
388
|
-
stripe: testSecret,
|
|
389
|
-
github: 'wrong-secret',
|
|
390
|
-
});
|
|
391
|
-
console.log(' โ
verifyAny:', result.isValid && result.platform === 'stripe' ? 'PASSED' : 'FAILED');
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
console.log(' โ verifyAny test failed:', error);
|
|
395
|
-
}
|
|
396
|
-
// Test 10.5: verifyAny error diagnostics
|
|
397
|
-
console.log('\n10.5. Testing verifyAny error diagnostics...');
|
|
398
|
-
try {
|
|
399
|
-
const unknownRequest = createMockRequest({
|
|
400
|
-
'content-type': 'application/json',
|
|
401
|
-
});
|
|
402
|
-
const invalidVerifyAny = await index_1.WebhookVerificationService.verifyAny(unknownRequest, {
|
|
403
|
-
stripe: testSecret,
|
|
404
|
-
shopify: testSecret,
|
|
405
|
-
});
|
|
406
|
-
const hasDetailedErrors = Boolean(invalidVerifyAny.error
|
|
407
|
-
&& invalidVerifyAny.error.includes('Attempts ->')
|
|
408
|
-
&& invalidVerifyAny.metadata?.attempts?.length === 2);
|
|
409
|
-
console.log(' โ
verifyAny diagnostics:', hasDetailedErrors ? 'PASSED' : 'FAILED');
|
|
410
|
-
}
|
|
411
|
-
catch (error) {
|
|
412
|
-
console.log(' โ verifyAny diagnostics test failed:', error);
|
|
413
|
-
}
|
|
414
|
-
// Test 11: Normalization for Stripe
|
|
415
|
-
console.log('\n11. Testing payload normalization...');
|
|
416
|
-
try {
|
|
417
|
-
const normalizedStripeBody = JSON.stringify({
|
|
418
|
-
type: 'payment_intent.succeeded',
|
|
419
|
-
data: {
|
|
420
|
-
object: {
|
|
421
|
-
id: 'pi_123',
|
|
422
|
-
amount: 5000,
|
|
423
|
-
currency: 'usd',
|
|
424
|
-
customer: 'cus_456',
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
429
|
-
const stripeSignature = createStripeSignature(normalizedStripeBody, testSecret, timestamp);
|
|
430
|
-
const request = createMockRequest({
|
|
431
|
-
'stripe-signature': stripeSignature,
|
|
432
|
-
'content-type': 'application/json',
|
|
433
|
-
}, normalizedStripeBody);
|
|
434
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'stripe', testSecret, 300, true);
|
|
435
|
-
const payload = result.payload;
|
|
436
|
-
const passed = result.isValid
|
|
437
|
-
&& payload.event === 'payment.succeeded'
|
|
438
|
-
&& payload.currency === 'USD'
|
|
439
|
-
&& payload.transaction_id === 'pi_123';
|
|
440
|
-
console.log(' โ
Normalization:', passed ? 'PASSED' : 'FAILED');
|
|
441
|
-
}
|
|
442
|
-
catch (error) {
|
|
443
|
-
console.log(' โ Normalization test failed:', error);
|
|
444
|
-
}
|
|
445
|
-
// Test 12: Category-aware normalization registry
|
|
446
|
-
console.log('\n12. Testing category-based platform registry...');
|
|
447
|
-
try {
|
|
448
|
-
const paymentPlatforms = (0, index_1.getPlatformsByCategory)('payment');
|
|
449
|
-
const hasStripeAndPolar = paymentPlatforms.includes('stripe') && paymentPlatforms.includes('polar');
|
|
450
|
-
console.log(' โ
Category registry:', hasStripeAndPolar ? 'PASSED' : 'FAILED');
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
console.log(' โ Category registry test failed:', error);
|
|
454
|
-
}
|
|
455
|
-
// Test 13: Razorpay
|
|
456
|
-
console.log('\n13. Testing Razorpay webhook...');
|
|
457
|
-
try {
|
|
458
|
-
const hmac = (0, crypto_1.createHmac)('sha256', testSecret);
|
|
459
|
-
hmac.update(testBody);
|
|
460
|
-
const signature = hmac.digest('hex');
|
|
461
|
-
const request = createMockRequest({
|
|
462
|
-
'x-razorpay-signature': signature,
|
|
463
|
-
'content-type': 'application/json',
|
|
464
|
-
});
|
|
465
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'razorpay', testSecret);
|
|
466
|
-
console.log(' โ
Razorpay:', result.isValid ? 'PASSED' : 'FAILED');
|
|
467
|
-
}
|
|
468
|
-
catch (error) {
|
|
469
|
-
console.log(' โ Razorpay test failed:', error);
|
|
470
|
-
}
|
|
471
|
-
// Test 14: Lemon Squeezy
|
|
472
|
-
console.log('\n14. Testing Lemon Squeezy webhook...');
|
|
473
|
-
try {
|
|
474
|
-
const hmac = (0, crypto_1.createHmac)('sha256', testSecret);
|
|
475
|
-
hmac.update(testBody);
|
|
476
|
-
const signature = hmac.digest('hex');
|
|
477
|
-
const request = createMockRequest({
|
|
478
|
-
'x-signature': signature,
|
|
479
|
-
'content-type': 'application/json',
|
|
480
|
-
});
|
|
481
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'lemonsqueezy', testSecret);
|
|
482
|
-
console.log(' โ
Lemon Squeezy:', result.isValid ? 'PASSED' : 'FAILED');
|
|
483
|
-
}
|
|
484
|
-
catch (error) {
|
|
485
|
-
console.log(' โ Lemon Squeezy test failed:', error);
|
|
486
|
-
}
|
|
487
|
-
// Test 15: Paddle
|
|
488
|
-
console.log('\n15. Testing Paddle webhook...');
|
|
489
|
-
try {
|
|
490
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
491
|
-
const signature = createPaddleSignature(testBody, testSecret, timestamp);
|
|
492
|
-
const request = createMockRequest({
|
|
493
|
-
'paddle-signature': signature,
|
|
494
|
-
'content-type': 'application/json',
|
|
495
|
-
});
|
|
496
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'paddle', testSecret);
|
|
497
|
-
console.log(' โ
Paddle:', result.isValid ? 'PASSED' : 'FAILED');
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
console.log(' โ Paddle test failed:', error);
|
|
501
|
-
}
|
|
502
|
-
// Test 16: WorkOS
|
|
503
|
-
console.log('\n16. Testing WorkOS webhook...');
|
|
504
|
-
try {
|
|
505
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
506
|
-
const signature = createWorkOSSignature(testBody, testSecret, timestamp);
|
|
507
|
-
const request = createMockRequest({
|
|
508
|
-
'workos-signature': signature,
|
|
509
|
-
'content-type': 'application/json',
|
|
510
|
-
});
|
|
511
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'workos', testSecret);
|
|
512
|
-
console.log(' โ
WorkOS:', result.isValid ? 'PASSED' : 'FAILED');
|
|
513
|
-
}
|
|
514
|
-
catch (error) {
|
|
515
|
-
console.log(' โ WorkOS test failed:', error);
|
|
516
|
-
}
|
|
517
|
-
// Test 17.5: Shopify
|
|
518
|
-
console.log('\n17. Testing Shopify webhook...');
|
|
519
|
-
try {
|
|
520
|
-
const signature = createShopifySignature(testBody, testSecret);
|
|
521
|
-
const request = createMockRequest({
|
|
522
|
-
'x-shopify-hmac-sha256': signature,
|
|
523
|
-
'content-type': 'application/json',
|
|
524
|
-
});
|
|
525
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'shopify', testSecret);
|
|
526
|
-
console.log(' โ
Shopify:', result.isValid ? 'PASSED' : 'FAILED');
|
|
527
|
-
if (!result.isValid) {
|
|
528
|
-
console.log(' โ Error:', result.error);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
catch (error) {
|
|
532
|
-
console.log(' โ Shopify test failed:', error);
|
|
533
|
-
}
|
|
534
|
-
// Test 18: WooCommerce
|
|
535
|
-
console.log('\n18. Testing WooCommerce webhook...');
|
|
536
|
-
try {
|
|
537
|
-
const signature = createWooCommerceSignature(testBody, testSecret);
|
|
538
|
-
const request = createMockRequest({
|
|
539
|
-
'x-wc-webhook-signature': signature,
|
|
540
|
-
'content-type': 'application/json',
|
|
541
|
-
});
|
|
542
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'woocommerce', testSecret);
|
|
543
|
-
console.log(' โ
WooCommerce:', result.isValid ? 'PASSED' : 'FAILED');
|
|
544
|
-
}
|
|
545
|
-
catch (error) {
|
|
546
|
-
console.log(' โ WooCommerce test failed:', error);
|
|
547
|
-
}
|
|
548
|
-
// Test 18.5: Polar (Standard Webhooks)
|
|
549
|
-
console.log('\n18.5. Testing Polar webhook...');
|
|
550
|
-
try {
|
|
551
|
-
const secret = `whsec_${Buffer.from(testSecret).toString('base64')}`;
|
|
552
|
-
const webhookId = 'polar-webhook-id-1';
|
|
553
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
554
|
-
const signature = createStandardWebhooksSignature(testBody, secret, webhookId, timestamp);
|
|
555
|
-
const request = createMockRequest({
|
|
556
|
-
'webhook-signature': signature,
|
|
557
|
-
'webhook-id': webhookId,
|
|
558
|
-
'webhook-timestamp': timestamp.toString(),
|
|
559
|
-
'user-agent': 'Polar.sh Webhooks',
|
|
560
|
-
'content-type': 'application/json',
|
|
561
|
-
});
|
|
562
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'polar', secret);
|
|
563
|
-
const detectedPlatform = index_1.WebhookVerificationService.detectPlatform(request);
|
|
564
|
-
console.log(' โ
Polar verification:', result.isValid ? 'PASSED' : 'FAILED');
|
|
565
|
-
console.log(' โ
Polar auto-detect:', detectedPlatform === 'polar' ? 'PASSED' : 'FAILED');
|
|
566
|
-
}
|
|
567
|
-
catch (error) {
|
|
568
|
-
console.log(' โ Polar test failed:', error);
|
|
569
|
-
}
|
|
570
|
-
// Test 19: Replicate
|
|
571
|
-
console.log('\n19. Testing Replicate webhook...');
|
|
572
|
-
try {
|
|
573
|
-
const secret = `whsec_${Buffer.from(testSecret).toString('base64')}`;
|
|
574
|
-
const webhookId = 'replicate-webhook-id-1';
|
|
575
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
576
|
-
const signature = createStandardWebhooksSignature(testBody, secret, webhookId, timestamp);
|
|
577
|
-
const request = createMockRequest({
|
|
578
|
-
'webhook-signature': signature,
|
|
579
|
-
'webhook-id': webhookId,
|
|
580
|
-
'webhook-timestamp': timestamp.toString(),
|
|
581
|
-
'user-agent': 'Replicate-Webhooks/1.0',
|
|
582
|
-
'content-type': 'application/json',
|
|
583
|
-
});
|
|
584
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'replicateai', secret);
|
|
585
|
-
console.log(' โ
Replicate:', result.isValid ? 'PASSED' : 'FAILED');
|
|
586
|
-
}
|
|
587
|
-
catch (error) {
|
|
588
|
-
console.log(' โ Replicate test failed:', error);
|
|
589
|
-
}
|
|
590
|
-
// Test 20: fal.ai
|
|
591
|
-
console.log('\n20. Testing fal.ai webhook...');
|
|
592
|
-
try {
|
|
593
|
-
const { privateKey, publicKey } = (0, crypto_1.generateKeyPairSync)('ed25519');
|
|
594
|
-
const requestId = 'fal-request-id';
|
|
595
|
-
const userId = 'fal-user-id';
|
|
596
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
597
|
-
const payloadToSign = createFalPayloadToSign(testBody, requestId, userId, timestamp);
|
|
598
|
-
const payloadBytes = new Uint8Array(Buffer.from(payloadToSign));
|
|
599
|
-
const signature = (0, crypto_1.sign)(null, payloadBytes, privateKey).toString('hex');
|
|
600
|
-
const request = createMockRequest({
|
|
601
|
-
'x-fal-webhook-signature': signature,
|
|
602
|
-
'x-fal-webhook-request-id': requestId,
|
|
603
|
-
'x-fal-webhook-user-id': userId,
|
|
604
|
-
'x-fal-webhook-timestamp': timestamp,
|
|
605
|
-
'content-type': 'application/json',
|
|
606
|
-
});
|
|
607
|
-
const result = await index_1.WebhookVerificationService.verify(request, {
|
|
608
|
-
platform: 'falai',
|
|
609
|
-
secret: '',
|
|
610
|
-
signatureConfig: {
|
|
611
|
-
algorithm: 'ed25519',
|
|
612
|
-
headerName: 'x-fal-webhook-signature',
|
|
613
|
-
headerFormat: 'raw',
|
|
614
|
-
payloadFormat: 'custom',
|
|
615
|
-
customConfig: {
|
|
616
|
-
publicKey: publicKey.export({ type: 'spki', format: 'pem' }).toString(),
|
|
617
|
-
requestIdHeader: 'x-fal-webhook-request-id',
|
|
618
|
-
userIdHeader: 'x-fal-webhook-user-id',
|
|
619
|
-
timestampHeader: 'x-fal-webhook-timestamp',
|
|
620
|
-
},
|
|
621
|
-
},
|
|
622
|
-
});
|
|
623
|
-
console.log(' โ
fal.ai:', result.isValid ? 'PASSED' : 'FAILED');
|
|
624
|
-
}
|
|
625
|
-
catch (error) {
|
|
626
|
-
console.log(' โ fal.ai test failed:', error);
|
|
627
|
-
}
|
|
628
|
-
// Test 21: Core SDK queue entry point
|
|
629
|
-
console.log('\n21. Testing core SDK handleWithQueue...');
|
|
630
|
-
try {
|
|
631
|
-
const request = createMockRequest({
|
|
632
|
-
'content-type': 'application/json',
|
|
633
|
-
});
|
|
634
|
-
const originalToken = process.env.QSTASH_TOKEN;
|
|
635
|
-
const originalCurrent = process.env.QSTASH_CURRENT_SIGNING_KEY;
|
|
636
|
-
const originalNext = process.env.QSTASH_NEXT_SIGNING_KEY;
|
|
637
|
-
delete process.env.QSTASH_TOKEN;
|
|
638
|
-
delete process.env.QSTASH_CURRENT_SIGNING_KEY;
|
|
639
|
-
delete process.env.QSTASH_NEXT_SIGNING_KEY;
|
|
640
|
-
let threw = false;
|
|
641
|
-
try {
|
|
642
|
-
await index_1.WebhookVerificationService.handleWithQueue(request, {
|
|
643
|
-
platform: 'stripe',
|
|
644
|
-
secret: testSecret,
|
|
645
|
-
queue: true,
|
|
646
|
-
handler: async () => undefined,
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
catch (error) {
|
|
650
|
-
threw = error.message.includes('queue: true requires QSTASH_TOKEN');
|
|
651
|
-
}
|
|
652
|
-
if (originalToken !== undefined)
|
|
653
|
-
process.env.QSTASH_TOKEN = originalToken;
|
|
654
|
-
if (originalCurrent !== undefined)
|
|
655
|
-
process.env.QSTASH_CURRENT_SIGNING_KEY = originalCurrent;
|
|
656
|
-
if (originalNext !== undefined)
|
|
657
|
-
process.env.QSTASH_NEXT_SIGNING_KEY = originalNext;
|
|
658
|
-
console.log(' โ
handleWithQueue:', threw ? 'PASSED' : 'FAILED');
|
|
659
|
-
}
|
|
660
|
-
catch (error) {
|
|
661
|
-
console.log(' โ handleWithQueue test failed:', error);
|
|
662
|
-
}
|
|
663
|
-
console.log('\n๐ All tests completed!');
|
|
664
|
-
}
|
|
665
|
-
// Run tests if this file is executed directly
|
|
666
|
-
if (require.main === module) {
|
|
667
|
-
runTests().catch(console.error);
|
|
668
|
-
}
|