@hookflo/tern 1.0.5 → 2.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 +70 -10
- package/dist/normalization/index.d.ts +20 -0
- package/dist/normalization/index.js +78 -0
- package/dist/normalization/providers/payment/paypal.d.ts +2 -0
- package/dist/normalization/providers/payment/paypal.js +12 -0
- package/dist/normalization/providers/payment/razorpay.d.ts +2 -0
- package/dist/normalization/providers/payment/razorpay.js +13 -0
- package/dist/normalization/providers/payment/stripe.d.ts +2 -0
- package/dist/normalization/providers/payment/stripe.js +13 -0
- package/dist/normalization/providers/registry.d.ts +5 -0
- package/dist/normalization/providers/registry.js +23 -0
- package/dist/normalization/storage/interface.d.ts +13 -0
- package/dist/normalization/storage/interface.js +2 -0
- package/dist/normalization/storage/memory.d.ts +12 -0
- package/dist/normalization/storage/memory.js +39 -0
- package/dist/normalization/templates/base/auth.d.ts +2 -0
- package/dist/normalization/templates/base/auth.js +22 -0
- package/dist/normalization/templates/base/ecommerce.d.ts +2 -0
- package/dist/normalization/templates/base/ecommerce.js +25 -0
- package/dist/normalization/templates/base/payment.d.ts +2 -0
- package/dist/normalization/templates/base/payment.js +25 -0
- package/dist/normalization/templates/registry.d.ts +6 -0
- package/dist/normalization/templates/registry.js +22 -0
- package/dist/normalization/transformer/engine.d.ts +11 -0
- package/dist/normalization/transformer/engine.js +86 -0
- package/dist/normalization/transformer/validator.d.ts +12 -0
- package/dist/normalization/transformer/validator.js +56 -0
- package/dist/normalization/types.d.ts +79 -0
- package/dist/normalization/types.js +2 -0
- package/dist/platforms/algorithms.d.ts +1 -1
- package/dist/platforms/algorithms.js +89 -88
- package/dist/test.js +8 -3
- package/dist/verifiers/algorithms.d.ts +2 -2
- package/dist/verifiers/algorithms.js +62 -63
- package/dist/verifiers/base.d.ts +1 -1
- package/dist/verifiers/custom-algorithms.d.ts +2 -2
- package/dist/verifiers/custom-algorithms.js +8 -8
- package/package.json +1 -1
|
@@ -15,28 +15,27 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
15
15
|
if (!headerValue)
|
|
16
16
|
return null;
|
|
17
17
|
switch (this.config.headerFormat) {
|
|
18
|
-
case
|
|
18
|
+
case 'prefixed':
|
|
19
19
|
// For GitHub, return the full signature including prefix for comparison
|
|
20
20
|
return headerValue;
|
|
21
|
-
case
|
|
21
|
+
case 'comma-separated':
|
|
22
22
|
// Handle comma-separated format like Stripe: "t=1234567890,v1=abc123"
|
|
23
|
-
const parts = headerValue.split(
|
|
23
|
+
const parts = headerValue.split(',');
|
|
24
24
|
const sigMap = {};
|
|
25
25
|
for (const part of parts) {
|
|
26
|
-
const [key, value] = part.split(
|
|
26
|
+
const [key, value] = part.split('=');
|
|
27
27
|
if (key && value) {
|
|
28
28
|
sigMap[key] = value;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
return sigMap.v1 || sigMap.signature || null;
|
|
32
|
-
case
|
|
32
|
+
case 'raw':
|
|
33
33
|
default:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const signatures = headerValue.split(" ");
|
|
34
|
+
if (this.platform === 'clerk' || this.platform === 'dodopayments') {
|
|
35
|
+
const signatures = headerValue.split(' ');
|
|
37
36
|
for (const sig of signatures) {
|
|
38
|
-
const [version, signature] = sig.split(
|
|
39
|
-
if (version ===
|
|
37
|
+
const [version, signature] = sig.split(',');
|
|
38
|
+
if (version === 'v1') {
|
|
40
39
|
return signature;
|
|
41
40
|
}
|
|
42
41
|
}
|
|
@@ -52,11 +51,11 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
52
51
|
if (!timestampHeader)
|
|
53
52
|
return null;
|
|
54
53
|
switch (this.config.timestampFormat) {
|
|
55
|
-
case
|
|
54
|
+
case 'unix':
|
|
56
55
|
return parseInt(timestampHeader, 10);
|
|
57
|
-
case
|
|
56
|
+
case 'iso':
|
|
58
57
|
return Math.floor(new Date(timestampHeader).getTime() / 1000);
|
|
59
|
-
case
|
|
58
|
+
case 'custom':
|
|
60
59
|
// Custom timestamp parsing logic can be added here
|
|
61
60
|
return parseInt(timestampHeader, 10);
|
|
62
61
|
default:
|
|
@@ -65,14 +64,14 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
65
64
|
}
|
|
66
65
|
extractTimestampFromSignature(request) {
|
|
67
66
|
// For platforms like Stripe where timestamp is embedded in signature
|
|
68
|
-
if (this.config.headerFormat ===
|
|
67
|
+
if (this.config.headerFormat === 'comma-separated') {
|
|
69
68
|
const headerValue = request.headers.get(this.config.headerName);
|
|
70
69
|
if (!headerValue)
|
|
71
70
|
return null;
|
|
72
|
-
const parts = headerValue.split(
|
|
71
|
+
const parts = headerValue.split(',');
|
|
73
72
|
const sigMap = {};
|
|
74
73
|
for (const part of parts) {
|
|
75
|
-
const [key, value] = part.split(
|
|
74
|
+
const [key, value] = part.split('=');
|
|
76
75
|
if (key && value) {
|
|
77
76
|
sigMap[key] = value;
|
|
78
77
|
}
|
|
@@ -83,14 +82,14 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
83
82
|
}
|
|
84
83
|
formatPayload(rawBody, request) {
|
|
85
84
|
switch (this.config.payloadFormat) {
|
|
86
|
-
case
|
|
85
|
+
case 'timestamped':
|
|
87
86
|
// For Stripe, timestamp is embedded in signature
|
|
88
|
-
const timestamp = this.extractTimestampFromSignature(request)
|
|
89
|
-
this.extractTimestamp(request);
|
|
87
|
+
const timestamp = this.extractTimestampFromSignature(request)
|
|
88
|
+
|| this.extractTimestamp(request);
|
|
90
89
|
return timestamp ? `${timestamp}.${rawBody}` : rawBody;
|
|
91
|
-
case
|
|
90
|
+
case 'custom':
|
|
92
91
|
return this.formatCustomPayload(rawBody, request);
|
|
93
|
-
case
|
|
92
|
+
case 'raw':
|
|
94
93
|
default:
|
|
95
94
|
return rawBody;
|
|
96
95
|
}
|
|
@@ -101,42 +100,42 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
101
100
|
}
|
|
102
101
|
const customFormat = this.config.customConfig.payloadFormat;
|
|
103
102
|
// Handle Clerk-style format: {id}.{timestamp}.{body}
|
|
104
|
-
if (customFormat.includes(
|
|
105
|
-
const id = request.headers.get(this.config.customConfig.idHeader ||
|
|
106
|
-
const timestamp = request.headers.get(this.config.timestampHeader ||
|
|
103
|
+
if (customFormat.includes('{id}') && customFormat.includes('{timestamp}')) {
|
|
104
|
+
const id = request.headers.get(this.config.customConfig.idHeader || 'x-webhook-id');
|
|
105
|
+
const timestamp = request.headers.get(this.config.timestampHeader || 'x-webhook-timestamp');
|
|
107
106
|
return customFormat
|
|
108
|
-
.replace(
|
|
109
|
-
.replace(
|
|
110
|
-
.replace(
|
|
107
|
+
.replace('{id}', id || '')
|
|
108
|
+
.replace('{timestamp}', timestamp || '')
|
|
109
|
+
.replace('{body}', rawBody);
|
|
111
110
|
}
|
|
112
111
|
// Handle Stripe-style format: {timestamp}.{body}
|
|
113
|
-
if (customFormat.includes(
|
|
114
|
-
customFormat.includes(
|
|
112
|
+
if (customFormat.includes('{timestamp}')
|
|
113
|
+
&& customFormat.includes('{body}')) {
|
|
115
114
|
const timestamp = this.extractTimestamp(request);
|
|
116
115
|
return customFormat
|
|
117
|
-
.replace(
|
|
118
|
-
.replace(
|
|
116
|
+
.replace('{timestamp}', timestamp?.toString() || '')
|
|
117
|
+
.replace('{body}', rawBody);
|
|
119
118
|
}
|
|
120
119
|
return rawBody;
|
|
121
120
|
}
|
|
122
|
-
verifyHMAC(payload, signature, algorithm =
|
|
121
|
+
verifyHMAC(payload, signature, algorithm = 'sha256') {
|
|
123
122
|
const hmac = (0, crypto_1.createHmac)(algorithm, this.secret);
|
|
124
123
|
hmac.update(payload);
|
|
125
|
-
const expectedSignature = hmac.digest(
|
|
124
|
+
const expectedSignature = hmac.digest('hex');
|
|
126
125
|
return this.safeCompare(signature, expectedSignature);
|
|
127
126
|
}
|
|
128
|
-
verifyHMACWithPrefix(payload, signature, algorithm =
|
|
127
|
+
verifyHMACWithPrefix(payload, signature, algorithm = 'sha256') {
|
|
129
128
|
const hmac = (0, crypto_1.createHmac)(algorithm, this.secret);
|
|
130
129
|
hmac.update(payload);
|
|
131
|
-
const expectedSignature = `${this.config.prefix ||
|
|
130
|
+
const expectedSignature = `${this.config.prefix || ''}${hmac.digest('hex')}`;
|
|
132
131
|
return this.safeCompare(signature, expectedSignature);
|
|
133
132
|
}
|
|
134
|
-
verifyHMACWithBase64(payload, signature, algorithm =
|
|
133
|
+
verifyHMACWithBase64(payload, signature, algorithm = 'sha256') {
|
|
135
134
|
// For platforms like Clerk that use base64 encoding
|
|
136
|
-
const secretBytes = new Uint8Array(Buffer.from(this.secret.split(
|
|
135
|
+
const secretBytes = new Uint8Array(Buffer.from(this.secret.split('_')[1], 'base64'));
|
|
137
136
|
const hmac = (0, crypto_1.createHmac)(algorithm, secretBytes);
|
|
138
137
|
hmac.update(payload);
|
|
139
|
-
const expectedSignature = hmac.digest(
|
|
138
|
+
const expectedSignature = hmac.digest('base64');
|
|
140
139
|
return this.safeCompare(signature, expectedSignature);
|
|
141
140
|
}
|
|
142
141
|
extractMetadata(request) {
|
|
@@ -150,18 +149,18 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
150
149
|
}
|
|
151
150
|
// Add platform-specific metadata
|
|
152
151
|
switch (this.platform) {
|
|
153
|
-
case
|
|
154
|
-
metadata.event = request.headers.get(
|
|
155
|
-
metadata.delivery = request.headers.get(
|
|
152
|
+
case 'github':
|
|
153
|
+
metadata.event = request.headers.get('x-github-event');
|
|
154
|
+
metadata.delivery = request.headers.get('x-github-delivery');
|
|
156
155
|
break;
|
|
157
|
-
case
|
|
156
|
+
case 'stripe':
|
|
158
157
|
// Extract Stripe-specific metadata from signature
|
|
159
158
|
const headerValue = request.headers.get(this.config.headerName);
|
|
160
|
-
if (headerValue && this.config.headerFormat ===
|
|
161
|
-
const parts = headerValue.split(
|
|
159
|
+
if (headerValue && this.config.headerFormat === 'comma-separated') {
|
|
160
|
+
const parts = headerValue.split(',');
|
|
162
161
|
const sigMap = {};
|
|
163
162
|
for (const part of parts) {
|
|
164
|
-
const [key, value] = part.split(
|
|
163
|
+
const [key, value] = part.split('=');
|
|
165
164
|
if (key && value) {
|
|
166
165
|
sigMap[key] = value;
|
|
167
166
|
}
|
|
@@ -169,8 +168,8 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
169
168
|
metadata.id = sigMap.id;
|
|
170
169
|
}
|
|
171
170
|
break;
|
|
172
|
-
case
|
|
173
|
-
metadata.id = request.headers.get(
|
|
171
|
+
case 'clerk':
|
|
172
|
+
metadata.id = request.headers.get('svix-id');
|
|
174
173
|
break;
|
|
175
174
|
}
|
|
176
175
|
return metadata;
|
|
@@ -192,7 +191,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
192
191
|
const rawBody = await request.text();
|
|
193
192
|
// Extract timestamp based on platform configuration
|
|
194
193
|
let timestamp = null;
|
|
195
|
-
if (this.config.headerFormat ===
|
|
194
|
+
if (this.config.headerFormat === 'comma-separated') {
|
|
196
195
|
// For platforms like Stripe where timestamp is embedded in signature
|
|
197
196
|
timestamp = this.extractTimestampFromSignature(request);
|
|
198
197
|
}
|
|
@@ -204,7 +203,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
204
203
|
if (timestamp && !this.isTimestampValid(timestamp)) {
|
|
205
204
|
return {
|
|
206
205
|
isValid: false,
|
|
207
|
-
error:
|
|
206
|
+
error: 'Webhook timestamp expired',
|
|
208
207
|
platform: this.platform,
|
|
209
208
|
};
|
|
210
209
|
}
|
|
@@ -212,12 +211,12 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
212
211
|
const payload = this.formatPayload(rawBody, request);
|
|
213
212
|
// Verify signature based on platform configuration
|
|
214
213
|
let isValid = false;
|
|
215
|
-
const algorithm = this.config.algorithm.replace(
|
|
216
|
-
if (this.config.customConfig?.encoding ===
|
|
214
|
+
const algorithm = this.config.algorithm.replace('hmac-', '');
|
|
215
|
+
if (this.config.customConfig?.encoding === 'base64') {
|
|
217
216
|
// For platforms like Clerk that use base64 encoding
|
|
218
217
|
isValid = this.verifyHMACWithBase64(payload, signature, algorithm);
|
|
219
218
|
}
|
|
220
|
-
else if (this.config.headerFormat ===
|
|
219
|
+
else if (this.config.headerFormat === 'prefixed') {
|
|
221
220
|
// For platforms like GitHub that use prefixed signatures
|
|
222
221
|
isValid = this.verifyHMACWithPrefix(payload, signature, algorithm);
|
|
223
222
|
}
|
|
@@ -228,7 +227,7 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
228
227
|
if (!isValid) {
|
|
229
228
|
return {
|
|
230
229
|
isValid: false,
|
|
231
|
-
error:
|
|
230
|
+
error: 'Invalid signature',
|
|
232
231
|
platform: this.platform,
|
|
233
232
|
};
|
|
234
233
|
}
|
|
@@ -261,33 +260,33 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
261
260
|
exports.GenericHMACVerifier = GenericHMACVerifier;
|
|
262
261
|
// Legacy verifiers for backward compatibility
|
|
263
262
|
class HMACSHA256Verifier extends GenericHMACVerifier {
|
|
264
|
-
constructor(secret, config, platform =
|
|
263
|
+
constructor(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
|
|
265
264
|
super(secret, config, platform, toleranceInSeconds);
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
267
|
exports.HMACSHA256Verifier = HMACSHA256Verifier;
|
|
269
268
|
class HMACSHA1Verifier extends GenericHMACVerifier {
|
|
270
|
-
constructor(secret, config, platform =
|
|
269
|
+
constructor(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
|
|
271
270
|
super(secret, config, platform, toleranceInSeconds);
|
|
272
271
|
}
|
|
273
272
|
}
|
|
274
273
|
exports.HMACSHA1Verifier = HMACSHA1Verifier;
|
|
275
274
|
class HMACSHA512Verifier extends GenericHMACVerifier {
|
|
276
|
-
constructor(secret, config, platform =
|
|
275
|
+
constructor(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
|
|
277
276
|
super(secret, config, platform, toleranceInSeconds);
|
|
278
277
|
}
|
|
279
278
|
}
|
|
280
279
|
exports.HMACSHA512Verifier = HMACSHA512Verifier;
|
|
281
280
|
// Factory function to create verifiers based on algorithm
|
|
282
|
-
function createAlgorithmVerifier(secret, config, platform =
|
|
281
|
+
function createAlgorithmVerifier(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
|
|
283
282
|
switch (config.algorithm) {
|
|
284
|
-
case
|
|
285
|
-
case
|
|
286
|
-
case
|
|
283
|
+
case 'hmac-sha256':
|
|
284
|
+
case 'hmac-sha1':
|
|
285
|
+
case 'hmac-sha512':
|
|
287
286
|
return new GenericHMACVerifier(secret, config, platform, toleranceInSeconds);
|
|
288
|
-
case
|
|
289
|
-
case
|
|
290
|
-
case
|
|
287
|
+
case 'rsa-sha256':
|
|
288
|
+
case 'ed25519':
|
|
289
|
+
case 'custom':
|
|
291
290
|
// These can be implemented as needed
|
|
292
291
|
throw new Error(`Algorithm ${config.algorithm} not yet implemented`);
|
|
293
292
|
default:
|
package/dist/verifiers/base.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { WebhookVerifier } from
|
|
2
|
-
import { WebhookVerificationResult, SignatureConfig } from
|
|
1
|
+
import { WebhookVerifier } from './base';
|
|
2
|
+
import { WebhookVerificationResult, SignatureConfig } from '../types';
|
|
3
3
|
export declare class TokenBasedVerifier extends WebhookVerifier {
|
|
4
4
|
private config;
|
|
5
5
|
constructor(secret: string, config: SignatureConfig, toleranceInSeconds?: number);
|
|
@@ -12,12 +12,12 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
|
|
|
12
12
|
async verify(request) {
|
|
13
13
|
try {
|
|
14
14
|
const token = request.headers.get(this.config.headerName);
|
|
15
|
-
const id = request.headers.get(this.config.customConfig?.idHeader ||
|
|
15
|
+
const id = request.headers.get(this.config.customConfig?.idHeader || 'x-webhook-id');
|
|
16
16
|
if (!token) {
|
|
17
17
|
return {
|
|
18
18
|
isValid: false,
|
|
19
19
|
error: `Missing token header: ${this.config.headerName}`,
|
|
20
|
-
platform:
|
|
20
|
+
platform: 'custom',
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
// Simple token comparison
|
|
@@ -25,8 +25,8 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
|
|
|
25
25
|
if (!isValid) {
|
|
26
26
|
return {
|
|
27
27
|
isValid: false,
|
|
28
|
-
error:
|
|
29
|
-
platform:
|
|
28
|
+
error: 'Invalid token',
|
|
29
|
+
platform: 'custom',
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
const rawBody = await request.text();
|
|
@@ -39,11 +39,11 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
|
|
|
39
39
|
}
|
|
40
40
|
return {
|
|
41
41
|
isValid: true,
|
|
42
|
-
platform:
|
|
42
|
+
platform: 'custom',
|
|
43
43
|
payload,
|
|
44
44
|
metadata: {
|
|
45
45
|
id,
|
|
46
|
-
algorithm:
|
|
46
|
+
algorithm: 'token-based',
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
}
|
|
@@ -51,7 +51,7 @@ class TokenBasedVerifier extends base_1.WebhookVerifier {
|
|
|
51
51
|
return {
|
|
52
52
|
isValid: false,
|
|
53
53
|
error: `Token-based verification error: ${error.message}`,
|
|
54
|
-
platform:
|
|
54
|
+
platform: 'custom',
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -61,7 +61,7 @@ exports.TokenBasedVerifier = TokenBasedVerifier;
|
|
|
61
61
|
function createCustomVerifier(secret, config, toleranceInSeconds = 300) {
|
|
62
62
|
const customType = config.customConfig?.type;
|
|
63
63
|
switch (customType) {
|
|
64
|
-
case
|
|
64
|
+
case 'token-based':
|
|
65
65
|
return new TokenBasedVerifier(secret, config, toleranceInSeconds);
|
|
66
66
|
default:
|
|
67
67
|
// Fallback to token-based for unknown custom types
|
package/package.json
CHANGED