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