@hookflo/tern 2.2.10 โ 3.0.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/README.md +34 -37
- package/dist/adapters/cloudflare.d.ts +3 -3
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/nextjs.d.ts +3 -3
- package/dist/adapters/nextjs.js +1 -1
- package/dist/adapters/shared.js +20 -20
- package/dist/adapters/testExpress.js +24 -25
- package/dist/examples.js +2 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +26 -6
- package/dist/normalization/providers/registry.js +0 -2
- package/dist/normalization/simple.js +0 -12
- package/dist/platforms/algorithms.d.ts +1 -1
- package/dist/platforms/algorithms.js +217 -184
- package/dist/test.js +124 -29
- package/dist/types.d.ts +8 -4
- package/dist/types.js +4 -2
- package/dist/utils.js +0 -7
- package/dist/verifiers/algorithms.d.ts +1 -0
- package/dist/verifiers/algorithms.js +61 -11
- package/dist/verifiers/base.d.ts +1 -1
- package/dist/verifiers/custom-algorithms.js +1 -1
- package/package.json +1 -1
package/dist/test.js
CHANGED
|
@@ -65,9 +65,35 @@ function createWorkOSSignature(body, secret, timestamp) {
|
|
|
65
65
|
hmac.update(signedPayload);
|
|
66
66
|
return `t=${timestamp},v1=${hmac.digest('hex')}`;
|
|
67
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
|
+
}
|
|
68
94
|
function createFalPayloadToSign(body, requestId, userId, timestamp) {
|
|
69
95
|
const bodyHash = (0, crypto_1.createHash)('sha256').update(body).digest('hex');
|
|
70
|
-
return `${requestId}
|
|
96
|
+
return `${requestId}\n${userId}\n${timestamp}\n${bodyHash}`;
|
|
71
97
|
}
|
|
72
98
|
async function runTests() {
|
|
73
99
|
console.log('๐งช Running Webhook Verification Tests...\n');
|
|
@@ -81,7 +107,8 @@ async function runTests() {
|
|
|
81
107
|
'content-type': 'application/json',
|
|
82
108
|
});
|
|
83
109
|
const stripeResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(stripeRequest, 'stripe', testSecret);
|
|
84
|
-
|
|
110
|
+
const stripePassed = stripeResult.isValid && Boolean(stripeResult.eventId?.startsWith('stripe:'));
|
|
111
|
+
console.log(' โ
Stripe:', stripePassed ? 'PASSED' : 'FAILED');
|
|
85
112
|
if (!stripeResult.isValid) {
|
|
86
113
|
console.log(' โ Error:', stripeResult.error);
|
|
87
114
|
}
|
|
@@ -181,7 +208,7 @@ async function runTests() {
|
|
|
181
208
|
catch (error) {
|
|
182
209
|
console.log(' โ Dodo Payments test failed:', error);
|
|
183
210
|
}
|
|
184
|
-
// Test 5: Token-based
|
|
211
|
+
// Test 5: Token-based authentication helper
|
|
185
212
|
console.log('\n5. Testing Token-based Authentication...');
|
|
186
213
|
try {
|
|
187
214
|
const webhookId = 'test-webhook-id';
|
|
@@ -191,8 +218,10 @@ async function runTests() {
|
|
|
191
218
|
'x-webhook-token': webhookToken,
|
|
192
219
|
'content-type': 'application/json',
|
|
193
220
|
});
|
|
194
|
-
const tokenResult = await index_1.WebhookVerificationService.
|
|
195
|
-
|
|
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');
|
|
196
225
|
if (!tokenResult.isValid) {
|
|
197
226
|
console.log(' โ Error:', tokenResult.error);
|
|
198
227
|
}
|
|
@@ -200,6 +229,88 @@ async function runTests() {
|
|
|
200
229
|
catch (error) {
|
|
201
230
|
console.log(' โ Token-based test failed:', error);
|
|
202
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
|
+
}
|
|
203
314
|
// Test 6: Invalid signatures
|
|
204
315
|
console.log('\n6. Testing Invalid Signatures...');
|
|
205
316
|
try {
|
|
@@ -388,24 +499,8 @@ async function runTests() {
|
|
|
388
499
|
catch (error) {
|
|
389
500
|
console.log(' โ Paddle test failed:', error);
|
|
390
501
|
}
|
|
391
|
-
// Test 16:
|
|
392
|
-
console.log('\n16. Testing
|
|
393
|
-
try {
|
|
394
|
-
const hmac = (0, crypto_1.createHmac)('sha256', testSecret);
|
|
395
|
-
hmac.update(testBody);
|
|
396
|
-
const signature = hmac.digest('hex');
|
|
397
|
-
const request = createMockRequest({
|
|
398
|
-
'x-auth0-signature': signature,
|
|
399
|
-
'content-type': 'application/json',
|
|
400
|
-
});
|
|
401
|
-
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'auth0', testSecret);
|
|
402
|
-
console.log(' โ
Auth0:', result.isValid ? 'PASSED' : 'FAILED');
|
|
403
|
-
}
|
|
404
|
-
catch (error) {
|
|
405
|
-
console.log(' โ Auth0 test failed:', error);
|
|
406
|
-
}
|
|
407
|
-
// Test 17: WorkOS
|
|
408
|
-
console.log('\n17. Testing WorkOS webhook...');
|
|
502
|
+
// Test 16: WorkOS
|
|
503
|
+
console.log('\n16. Testing WorkOS webhook...');
|
|
409
504
|
try {
|
|
410
505
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
411
506
|
const signature = createWorkOSSignature(testBody, testSecret, timestamp);
|
|
@@ -420,7 +515,7 @@ async function runTests() {
|
|
|
420
515
|
console.log(' โ WorkOS test failed:', error);
|
|
421
516
|
}
|
|
422
517
|
// Test 17.5: Shopify
|
|
423
|
-
console.log('\n17.
|
|
518
|
+
console.log('\n17. Testing Shopify webhook...');
|
|
424
519
|
try {
|
|
425
520
|
const signature = createShopifySignature(testBody, testSecret);
|
|
426
521
|
const request = createMockRequest({
|
|
@@ -501,11 +596,11 @@ async function runTests() {
|
|
|
501
596
|
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
502
597
|
const payloadToSign = createFalPayloadToSign(testBody, requestId, userId, timestamp);
|
|
503
598
|
const payloadBytes = new Uint8Array(Buffer.from(payloadToSign));
|
|
504
|
-
const signature = (0, crypto_1.sign)(null, payloadBytes, privateKey).toString('
|
|
599
|
+
const signature = (0, crypto_1.sign)(null, payloadBytes, privateKey).toString('hex');
|
|
505
600
|
const request = createMockRequest({
|
|
506
601
|
'x-fal-webhook-signature': signature,
|
|
507
|
-
'x-fal-request-id': requestId,
|
|
508
|
-
'x-fal-user-id': userId,
|
|
602
|
+
'x-fal-webhook-request-id': requestId,
|
|
603
|
+
'x-fal-webhook-user-id': userId,
|
|
509
604
|
'x-fal-webhook-timestamp': timestamp,
|
|
510
605
|
'content-type': 'application/json',
|
|
511
606
|
});
|
|
@@ -519,8 +614,8 @@ async function runTests() {
|
|
|
519
614
|
payloadFormat: 'custom',
|
|
520
615
|
customConfig: {
|
|
521
616
|
publicKey: publicKey.export({ type: 'spki', format: 'pem' }).toString(),
|
|
522
|
-
requestIdHeader: 'x-fal-request-id',
|
|
523
|
-
userIdHeader: 'x-fal-user-id',
|
|
617
|
+
requestIdHeader: 'x-fal-webhook-request-id',
|
|
618
|
+
userIdHeader: 'x-fal-webhook-user-id',
|
|
524
619
|
timestampHeader: 'x-fal-webhook-timestamp',
|
|
525
620
|
},
|
|
526
621
|
},
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type WebhookPlatform = 'custom' | 'clerk' | '
|
|
1
|
+
export type WebhookPlatform = 'custom' | 'clerk' | 'github' | 'stripe' | 'shopify' | 'vercel' | 'polar' | 'dodopayments' | 'gitlab' | 'paddle' | 'razorpay' | 'lemonsqueezy' | 'workos' | 'woocommerce' | 'replicateai' | 'falai' | 'sentry' | 'grafana' | 'doppler' | 'sanity' | 'unknown';
|
|
2
2
|
export declare enum WebhookPlatformKeys {
|
|
3
3
|
GitHub = "github",
|
|
4
4
|
Stripe = "stripe",
|
|
@@ -7,16 +7,18 @@ export declare enum WebhookPlatformKeys {
|
|
|
7
7
|
Shopify = "shopify",
|
|
8
8
|
Vercel = "vercel",
|
|
9
9
|
Polar = "polar",
|
|
10
|
-
Supabase = "supabase",
|
|
11
10
|
GitLab = "gitlab",
|
|
12
11
|
Paddle = "paddle",
|
|
13
12
|
Razorpay = "razorpay",
|
|
14
13
|
LemonSqueezy = "lemonsqueezy",
|
|
15
|
-
Auth0 = "auth0",
|
|
16
14
|
WorkOS = "workos",
|
|
17
15
|
WooCommerce = "woocommerce",
|
|
18
16
|
ReplicateAI = "replicateai",
|
|
19
17
|
FalAI = "falai",
|
|
18
|
+
Sentry = "sentry",
|
|
19
|
+
Grafana = "grafana",
|
|
20
|
+
Doppler = "doppler",
|
|
21
|
+
Sanity = "sanity",
|
|
20
22
|
Custom = "custom",
|
|
21
23
|
Unknown = "unknown"
|
|
22
24
|
}
|
|
@@ -28,7 +30,8 @@ export interface SignatureConfig {
|
|
|
28
30
|
prefix?: string;
|
|
29
31
|
timestampHeader?: string;
|
|
30
32
|
timestampFormat?: 'unix' | 'iso' | 'custom';
|
|
31
|
-
payloadFormat?: 'raw' | 'timestamped' | 'custom';
|
|
33
|
+
payloadFormat?: 'raw' | 'timestamped' | 'json-stringified' | 'custom';
|
|
34
|
+
idHeader?: string;
|
|
32
35
|
customConfig?: Record<string, any>;
|
|
33
36
|
}
|
|
34
37
|
export type WebhookErrorCode = 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'TIMESTAMP_EXPIRED' | 'MISSING_TOKEN' | 'INVALID_TOKEN' | 'PLATFORM_NOT_SUPPORTED' | 'NORMALIZATION_ERROR' | 'VERIFICATION_ERROR';
|
|
@@ -101,6 +104,7 @@ export interface WebhookVerificationResult<TPayload = unknown> {
|
|
|
101
104
|
errorCode?: WebhookErrorCode;
|
|
102
105
|
platform: WebhookPlatform;
|
|
103
106
|
payload?: TPayload;
|
|
107
|
+
eventId?: string;
|
|
104
108
|
metadata?: {
|
|
105
109
|
timestamp?: string;
|
|
106
110
|
id?: string | null;
|
package/dist/types.js
CHANGED
|
@@ -10,16 +10,18 @@ var WebhookPlatformKeys;
|
|
|
10
10
|
WebhookPlatformKeys["Shopify"] = "shopify";
|
|
11
11
|
WebhookPlatformKeys["Vercel"] = "vercel";
|
|
12
12
|
WebhookPlatformKeys["Polar"] = "polar";
|
|
13
|
-
WebhookPlatformKeys["Supabase"] = "supabase";
|
|
14
13
|
WebhookPlatformKeys["GitLab"] = "gitlab";
|
|
15
14
|
WebhookPlatformKeys["Paddle"] = "paddle";
|
|
16
15
|
WebhookPlatformKeys["Razorpay"] = "razorpay";
|
|
17
16
|
WebhookPlatformKeys["LemonSqueezy"] = "lemonsqueezy";
|
|
18
|
-
WebhookPlatformKeys["Auth0"] = "auth0";
|
|
19
17
|
WebhookPlatformKeys["WorkOS"] = "workos";
|
|
20
18
|
WebhookPlatformKeys["WooCommerce"] = "woocommerce";
|
|
21
19
|
WebhookPlatformKeys["ReplicateAI"] = "replicateai";
|
|
22
20
|
WebhookPlatformKeys["FalAI"] = "falai";
|
|
21
|
+
WebhookPlatformKeys["Sentry"] = "sentry";
|
|
22
|
+
WebhookPlatformKeys["Grafana"] = "grafana";
|
|
23
|
+
WebhookPlatformKeys["Doppler"] = "doppler";
|
|
24
|
+
WebhookPlatformKeys["Sanity"] = "sanity";
|
|
23
25
|
WebhookPlatformKeys["Custom"] = "custom";
|
|
24
26
|
WebhookPlatformKeys["Unknown"] = "unknown";
|
|
25
27
|
})(WebhookPlatformKeys || (exports.WebhookPlatformKeys = WebhookPlatformKeys = {}));
|
package/dist/utils.js
CHANGED
|
@@ -131,9 +131,6 @@ function detectPlatformFromHeaders(headers) {
|
|
|
131
131
|
if (headerMap.has('x-signature')) {
|
|
132
132
|
return 'lemonsqueezy';
|
|
133
133
|
}
|
|
134
|
-
if (headerMap.has('x-auth0-signature')) {
|
|
135
|
-
return 'auth0';
|
|
136
|
-
}
|
|
137
134
|
if (headerMap.has('x-wc-webhook-signature')) {
|
|
138
135
|
return 'woocommerce';
|
|
139
136
|
}
|
|
@@ -158,10 +155,6 @@ function detectPlatformFromHeaders(headers) {
|
|
|
158
155
|
return 'polar';
|
|
159
156
|
}
|
|
160
157
|
}
|
|
161
|
-
// Supabase
|
|
162
|
-
if (headerMap.has('x-webhook-token')) {
|
|
163
|
-
return 'supabase';
|
|
164
|
-
}
|
|
165
158
|
return null;
|
|
166
159
|
}
|
|
167
160
|
/**
|
|
@@ -17,6 +17,7 @@ export declare abstract class AlgorithmBasedVerifier extends WebhookVerifier {
|
|
|
17
17
|
protected extractMetadata(request: Request): Record<string, any>;
|
|
18
18
|
}
|
|
19
19
|
export declare class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
20
|
+
private resolveSentryPayloadCandidates;
|
|
20
21
|
verify(request: Request): Promise<WebhookVerificationResult>;
|
|
21
22
|
}
|
|
22
23
|
export declare class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
@@ -93,6 +93,13 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
93
93
|
this.extractTimestamp(request);
|
|
94
94
|
return timestamp ? `${timestamp}.${rawBody}` : rawBody;
|
|
95
95
|
}
|
|
96
|
+
case "json-stringified":
|
|
97
|
+
try {
|
|
98
|
+
return JSON.stringify(JSON.parse(rawBody));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return rawBody;
|
|
102
|
+
}
|
|
96
103
|
case "custom":
|
|
97
104
|
return this.formatCustomPayload(rawBody, request);
|
|
98
105
|
case "raw":
|
|
@@ -178,9 +185,14 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
178
185
|
metadata.id = request.headers.get(this.config.customConfig?.idHeader || "webhook-id");
|
|
179
186
|
break;
|
|
180
187
|
case "workos":
|
|
181
|
-
|
|
188
|
+
case "sentry":
|
|
189
|
+
case "sanity":
|
|
190
|
+
metadata.id = request.headers.get(this.config.idHeader || this.config.customConfig?.idHeader || "webhook-id");
|
|
182
191
|
break;
|
|
183
192
|
default:
|
|
193
|
+
if (this.config.idHeader) {
|
|
194
|
+
metadata.id = request.headers.get(this.config.idHeader);
|
|
195
|
+
}
|
|
184
196
|
break;
|
|
185
197
|
}
|
|
186
198
|
return metadata;
|
|
@@ -188,6 +200,30 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
188
200
|
}
|
|
189
201
|
exports.AlgorithmBasedVerifier = AlgorithmBasedVerifier;
|
|
190
202
|
class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
203
|
+
resolveSentryPayloadCandidates(rawBody, request) {
|
|
204
|
+
const candidates = [this.formatPayload(rawBody, request), rawBody];
|
|
205
|
+
if (this.config.payloadFormat === "json-stringified") {
|
|
206
|
+
try {
|
|
207
|
+
const parsed = JSON.parse(rawBody);
|
|
208
|
+
candidates.push(JSON.stringify(parsed));
|
|
209
|
+
const issueAlertPath = this.config.customConfig?.issueAlertPayloadPath;
|
|
210
|
+
if (issueAlertPath) {
|
|
211
|
+
const segments = `${issueAlertPath}`.split(".").filter(Boolean);
|
|
212
|
+
let current = parsed;
|
|
213
|
+
for (const segment of segments) {
|
|
214
|
+
current = current?.[segment];
|
|
215
|
+
}
|
|
216
|
+
if (current !== undefined) {
|
|
217
|
+
candidates.push(typeof current === "string" ? current : JSON.stringify(current));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// ignore malformed JSON payloads
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return [...new Set(candidates.filter(Boolean))];
|
|
226
|
+
}
|
|
191
227
|
async verify(request) {
|
|
192
228
|
try {
|
|
193
229
|
const signature = this.extractSignature(request);
|
|
@@ -215,17 +251,24 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
215
251
|
platform: this.platform,
|
|
216
252
|
};
|
|
217
253
|
}
|
|
218
|
-
const
|
|
254
|
+
const payloadCandidates = this.platform === "sentry"
|
|
255
|
+
? this.resolveSentryPayloadCandidates(rawBody, request)
|
|
256
|
+
: [this.formatPayload(rawBody, request)];
|
|
219
257
|
let isValid = false;
|
|
220
258
|
const algorithm = this.config.algorithm.replace("hmac-", "");
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
259
|
+
for (const payload of payloadCandidates) {
|
|
260
|
+
if (this.config.customConfig?.encoding === "base64") {
|
|
261
|
+
isValid = this.verifyHMACWithBase64(payload, signature, algorithm);
|
|
262
|
+
}
|
|
263
|
+
else if (this.config.headerFormat === "prefixed") {
|
|
264
|
+
isValid = this.verifyHMACWithPrefix(payload, signature, algorithm);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
isValid = this.verifyHMAC(payload, signature, algorithm);
|
|
268
|
+
}
|
|
269
|
+
if (isValid) {
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
229
272
|
}
|
|
230
273
|
if (!isValid) {
|
|
231
274
|
return {
|
|
@@ -242,11 +285,18 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
242
285
|
catch (e) {
|
|
243
286
|
parsedPayload = rawBody;
|
|
244
287
|
}
|
|
288
|
+
const metadata = this.extractMetadata(request);
|
|
289
|
+
if (this.platform === "doppler" && !metadata.id) {
|
|
290
|
+
const timestamp = metadata.timestamp || Math.floor(Date.now() / 1000).toString();
|
|
291
|
+
metadata.id = (0, crypto_1.createHash)("sha256")
|
|
292
|
+
.update(`${timestamp}:${rawBody}`)
|
|
293
|
+
.digest("hex");
|
|
294
|
+
}
|
|
245
295
|
return {
|
|
246
296
|
isValid: true,
|
|
247
297
|
platform: this.platform,
|
|
248
298
|
payload: parsedPayload,
|
|
249
|
-
metadata
|
|
299
|
+
metadata,
|
|
250
300
|
};
|
|
251
301
|
}
|
|
252
302
|
catch (error) {
|
package/dist/verifiers/base.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TokenBasedVerifier = void 0;
|
|
4
4
|
exports.createCustomVerifier = createCustomVerifier;
|
|
5
5
|
const base_1 = require("./base");
|
|
6
|
-
// Token-based verifier for
|
|
6
|
+
// Token-based verifier for providers that use header token authentication
|
|
7
7
|
class TokenBasedVerifier extends base_1.WebhookVerifier {
|
|
8
8
|
constructor(secret, config, toleranceInSeconds = 300) {
|
|
9
9
|
super(secret, toleranceInSeconds);
|
package/package.json
CHANGED