@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.
Files changed (37) hide show
  1. package/README.md +70 -10
  2. package/dist/normalization/index.d.ts +20 -0
  3. package/dist/normalization/index.js +78 -0
  4. package/dist/normalization/providers/payment/paypal.d.ts +2 -0
  5. package/dist/normalization/providers/payment/paypal.js +12 -0
  6. package/dist/normalization/providers/payment/razorpay.d.ts +2 -0
  7. package/dist/normalization/providers/payment/razorpay.js +13 -0
  8. package/dist/normalization/providers/payment/stripe.d.ts +2 -0
  9. package/dist/normalization/providers/payment/stripe.js +13 -0
  10. package/dist/normalization/providers/registry.d.ts +5 -0
  11. package/dist/normalization/providers/registry.js +23 -0
  12. package/dist/normalization/storage/interface.d.ts +13 -0
  13. package/dist/normalization/storage/interface.js +2 -0
  14. package/dist/normalization/storage/memory.d.ts +12 -0
  15. package/dist/normalization/storage/memory.js +39 -0
  16. package/dist/normalization/templates/base/auth.d.ts +2 -0
  17. package/dist/normalization/templates/base/auth.js +22 -0
  18. package/dist/normalization/templates/base/ecommerce.d.ts +2 -0
  19. package/dist/normalization/templates/base/ecommerce.js +25 -0
  20. package/dist/normalization/templates/base/payment.d.ts +2 -0
  21. package/dist/normalization/templates/base/payment.js +25 -0
  22. package/dist/normalization/templates/registry.d.ts +6 -0
  23. package/dist/normalization/templates/registry.js +22 -0
  24. package/dist/normalization/transformer/engine.d.ts +11 -0
  25. package/dist/normalization/transformer/engine.js +86 -0
  26. package/dist/normalization/transformer/validator.d.ts +12 -0
  27. package/dist/normalization/transformer/validator.js +56 -0
  28. package/dist/normalization/types.d.ts +79 -0
  29. package/dist/normalization/types.js +2 -0
  30. package/dist/platforms/algorithms.d.ts +1 -1
  31. package/dist/platforms/algorithms.js +89 -89
  32. package/dist/verifiers/algorithms.d.ts +2 -2
  33. package/dist/verifiers/algorithms.js +62 -62
  34. package/dist/verifiers/base.d.ts +1 -1
  35. package/dist/verifiers/custom-algorithms.d.ts +2 -2
  36. package/dist/verifiers/custom-algorithms.js +8 -8
  37. 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 "prefixed":
18
+ case 'prefixed':
19
19
  // For GitHub, return the full signature including prefix for comparison
20
20
  return headerValue;
21
- case "comma-separated":
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 "raw":
32
+ case 'raw':
33
33
  default:
34
- if (this.platform === "clerk" || this.platform === "dodopayments") {
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 === "v1") {
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 "unix":
54
+ case 'unix':
55
55
  return parseInt(timestampHeader, 10);
56
- case "iso":
56
+ case 'iso':
57
57
  return Math.floor(new Date(timestampHeader).getTime() / 1000);
58
- case "custom":
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 === "comma-separated") {
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 "timestamped":
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 "custom":
90
+ case 'custom':
91
91
  return this.formatCustomPayload(rawBody, request);
92
- case "raw":
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("{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");
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("{id}", id || "")
108
- .replace("{timestamp}", timestamp || "")
109
- .replace("{body}", rawBody);
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("{timestamp}") &&
113
- customFormat.includes("{body}")) {
112
+ if (customFormat.includes('{timestamp}')
113
+ && customFormat.includes('{body}')) {
114
114
  const timestamp = this.extractTimestamp(request);
115
115
  return customFormat
116
- .replace("{timestamp}", timestamp?.toString() || "")
117
- .replace("{body}", rawBody);
116
+ .replace('{timestamp}', timestamp?.toString() || '')
117
+ .replace('{body}', rawBody);
118
118
  }
119
119
  return rawBody;
120
120
  }
121
- verifyHMAC(payload, signature, algorithm = "sha256") {
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("hex");
124
+ const expectedSignature = hmac.digest('hex');
125
125
  return this.safeCompare(signature, expectedSignature);
126
126
  }
127
- verifyHMACWithPrefix(payload, signature, algorithm = "sha256") {
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 || ""}${hmac.digest("hex")}`;
130
+ const expectedSignature = `${this.config.prefix || ''}${hmac.digest('hex')}`;
131
131
  return this.safeCompare(signature, expectedSignature);
132
132
  }
133
- verifyHMACWithBase64(payload, signature, algorithm = "sha256") {
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("_")[1], "base64"));
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("base64");
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 "github":
153
- metadata.event = request.headers.get("x-github-event");
154
- metadata.delivery = request.headers.get("x-github-delivery");
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 "stripe":
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 === "comma-separated") {
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 "clerk":
172
- metadata.id = request.headers.get("svix-id");
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 === "comma-separated") {
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: "Webhook timestamp expired",
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("hmac-", "");
215
- if (this.config.customConfig?.encoding === "base64") {
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 === "prefixed") {
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: "Invalid signature",
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 = "unknown", toleranceInSeconds = 300) {
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 = "unknown", toleranceInSeconds = 300) {
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 = "unknown", toleranceInSeconds = 300) {
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 = "unknown", toleranceInSeconds = 300) {
281
+ function createAlgorithmVerifier(secret, config, platform = 'unknown', toleranceInSeconds = 300) {
282
282
  switch (config.algorithm) {
283
- case "hmac-sha256":
284
- case "hmac-sha1":
285
- case "hmac-sha512":
283
+ case 'hmac-sha256':
284
+ case 'hmac-sha1':
285
+ case 'hmac-sha512':
286
286
  return new GenericHMACVerifier(secret, config, platform, toleranceInSeconds);
287
- case "rsa-sha256":
288
- case "ed25519":
289
- case "custom":
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:
@@ -1,4 +1,4 @@
1
- import { WebhookVerificationResult } from "../types";
1
+ import { WebhookVerificationResult } from '../types';
2
2
  export declare abstract class WebhookVerifier {
3
3
  protected secret: string;
4
4
  protected toleranceInSeconds: number;
@@ -1,5 +1,5 @@
1
- import { WebhookVerifier } from "./base";
2
- import { WebhookVerificationResult, SignatureConfig } from "../types";
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 || "x-webhook-id");
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: "custom",
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: "Invalid token",
29
- platform: "custom",
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: "custom",
42
+ platform: 'custom',
43
43
  payload,
44
44
  metadata: {
45
45
  id,
46
- algorithm: "token-based",
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: "custom",
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 "token-based":
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hookflo/tern",
3
- "version": "1.0.6",
3
+ "version": "2.0.0",
4
4
  "description": "A robust, scalable webhook verification framework supporting multiple platforms and signature algorithms",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",