@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.
Files changed (38) 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 -88
  32. package/dist/test.js +8 -3
  33. package/dist/verifiers/algorithms.d.ts +2 -2
  34. package/dist/verifiers/algorithms.js +62 -63
  35. package/dist/verifiers/base.d.ts +1 -1
  36. package/dist/verifiers/custom-algorithms.d.ts +2 -2
  37. package/dist/verifiers/custom-algorithms.js +8 -8
  38. package/package.json +1 -1
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SchemaValidator = void 0;
4
+ class SchemaValidator {
5
+ validateSchema(userSchema, baseTemplate) {
6
+ const errors = [];
7
+ // Ensure required base fields exist and are enabled or have defaults
8
+ for (const baseField of baseTemplate.fields) {
9
+ if (!baseField.required)
10
+ continue;
11
+ const userField = userSchema.fields.find((f) => f.id === baseField.id);
12
+ if (!userField) {
13
+ errors.push(`Missing required field in schema: ${baseField.id}`);
14
+ continue;
15
+ }
16
+ if (!userField.enabled && baseField.defaultValue === undefined) {
17
+ errors.push(`Required field disabled without default: ${baseField.id}`);
18
+ }
19
+ if (userField.type !== baseField.type) {
20
+ errors.push(`Type mismatch for field ${baseField.id}: expected ${baseField.type}, got ${userField.type}`);
21
+ }
22
+ }
23
+ return { valid: errors.length === 0, errors };
24
+ }
25
+ validateOutput(output, userSchema, baseTemplate) {
26
+ const errors = [];
27
+ for (const field of userSchema.fields) {
28
+ if (!field.enabled)
29
+ continue;
30
+ const value = output[field.name];
31
+ if (value === undefined) {
32
+ if (field.required)
33
+ errors.push(`Missing required field in output: ${field.name}`);
34
+ continue;
35
+ }
36
+ if (!this.matchesType(value, field.type)) {
37
+ errors.push(`Type mismatch for output field ${field.name}`);
38
+ }
39
+ }
40
+ return { valid: errors.length === 0, errors };
41
+ }
42
+ matchesType(value, type) {
43
+ if (type === 'number')
44
+ return typeof value === 'number' && !Number.isNaN(value);
45
+ if (type === 'string')
46
+ return typeof value === 'string';
47
+ if (type === 'boolean')
48
+ return typeof value === 'boolean';
49
+ if (type === 'object')
50
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
51
+ if (type === 'array')
52
+ return Array.isArray(value);
53
+ return true;
54
+ }
55
+ }
56
+ exports.SchemaValidator = SchemaValidator;
@@ -0,0 +1,79 @@
1
+ export type TemplateCategory = 'payment' | 'auth' | 'ecommerce';
2
+ export interface TemplateField {
3
+ id: string;
4
+ name: string;
5
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
6
+ required: boolean;
7
+ description?: string;
8
+ defaultValue?: unknown;
9
+ }
10
+ export interface BaseTemplate {
11
+ id: string;
12
+ category: TemplateCategory;
13
+ version: string;
14
+ fields: TemplateField[];
15
+ }
16
+ export interface UserSchemaField {
17
+ id: string;
18
+ name: string;
19
+ type: TemplateField['type'];
20
+ required: boolean;
21
+ enabled: boolean;
22
+ defaultValue?: unknown;
23
+ }
24
+ export interface FieldMapping {
25
+ schemaFieldId: string;
26
+ providerPath: string;
27
+ transform?: string;
28
+ }
29
+ export interface ProviderMapping {
30
+ provider: string;
31
+ fieldMappings: FieldMapping[];
32
+ }
33
+ export interface UserSchema {
34
+ id: string;
35
+ userId: string;
36
+ baseTemplateId: string;
37
+ category: TemplateCategory;
38
+ fields: UserSchemaField[];
39
+ providerMappings: ProviderMapping[];
40
+ createdAt: Date;
41
+ updatedAt: Date;
42
+ }
43
+ export interface NormalizedPayloadMeta {
44
+ provider: string;
45
+ schemaId: string;
46
+ schemaVersion: string;
47
+ transformedAt: Date;
48
+ }
49
+ export interface NormalizedResult {
50
+ normalized: Record<string, unknown>;
51
+ meta: NormalizedPayloadMeta;
52
+ }
53
+ export interface CreateSchemaInput {
54
+ userId: string;
55
+ baseTemplateId: string;
56
+ category: TemplateCategory;
57
+ fields: UserSchemaField[];
58
+ providerMappings: ProviderMapping[];
59
+ }
60
+ export interface UpdateSchemaInput {
61
+ fields?: UserSchemaField[];
62
+ providerMappings?: ProviderMapping[];
63
+ }
64
+ export interface ProviderInfoField {
65
+ path: string;
66
+ type?: TemplateField['type'];
67
+ description?: string;
68
+ }
69
+ export interface ProviderInfo {
70
+ id: string;
71
+ name: string;
72
+ category: TemplateCategory;
73
+ samplePaths?: ProviderInfoField[];
74
+ }
75
+ export interface TransformParams {
76
+ rawPayload: unknown;
77
+ provider: string;
78
+ schemaId: string;
79
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,4 @@
1
- import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from "../types";
1
+ import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from '../types';
2
2
  export declare const platformAlgorithmConfigs: Record<WebhookPlatform, PlatformAlgorithmConfig>;
3
3
  export declare function getPlatformAlgorithmConfig(platform: WebhookPlatform): PlatformAlgorithmConfig;
4
4
  export declare function platformUsesAlgorithm(platform: WebhookPlatform, algorithm: string): boolean;
@@ -7,138 +7,139 @@ exports.getPlatformsUsingAlgorithm = getPlatformsUsingAlgorithm;
7
7
  exports.validateSignatureConfig = validateSignatureConfig;
8
8
  exports.platformAlgorithmConfigs = {
9
9
  github: {
10
- platform: "github",
10
+ platform: 'github',
11
11
  signatureConfig: {
12
- algorithm: "hmac-sha256",
13
- headerName: "x-hub-signature-256",
14
- headerFormat: "prefixed",
15
- prefix: "sha256=",
12
+ algorithm: 'hmac-sha256',
13
+ headerName: 'x-hub-signature-256',
14
+ headerFormat: 'prefixed',
15
+ prefix: 'sha256=',
16
16
  timestampHeader: undefined,
17
- payloadFormat: "raw",
17
+ payloadFormat: 'raw',
18
18
  },
19
- description: "GitHub webhooks use HMAC-SHA256 with sha256= prefix",
19
+ description: 'GitHub webhooks use HMAC-SHA256 with sha256= prefix',
20
20
  },
21
21
  stripe: {
22
- platform: "stripe",
22
+ platform: 'stripe',
23
23
  signatureConfig: {
24
- algorithm: "hmac-sha256",
25
- headerName: "stripe-signature",
26
- headerFormat: "comma-separated",
24
+ algorithm: 'hmac-sha256',
25
+ headerName: 'stripe-signature',
26
+ headerFormat: 'comma-separated',
27
27
  timestampHeader: undefined,
28
- payloadFormat: "timestamped",
28
+ payloadFormat: 'timestamped',
29
29
  customConfig: {
30
- signatureFormat: "t={timestamp},v1={signature}",
30
+ signatureFormat: 't={timestamp},v1={signature}',
31
31
  },
32
32
  },
33
- description: "Stripe webhooks use HMAC-SHA256 with comma-separated format",
33
+ description: 'Stripe webhooks use HMAC-SHA256 with comma-separated format',
34
34
  },
35
35
  clerk: {
36
- platform: "clerk",
36
+ platform: 'clerk',
37
37
  signatureConfig: {
38
- algorithm: "hmac-sha256",
39
- headerName: "svix-signature",
40
- headerFormat: "raw",
41
- timestampHeader: "svix-timestamp",
42
- timestampFormat: "unix",
43
- payloadFormat: "custom",
38
+ algorithm: 'hmac-sha256',
39
+ headerName: 'svix-signature',
40
+ headerFormat: 'raw',
41
+ timestampHeader: 'svix-timestamp',
42
+ timestampFormat: 'unix',
43
+ payloadFormat: 'custom',
44
44
  customConfig: {
45
- signatureFormat: "v1={signature}",
46
- payloadFormat: "{id}.{timestamp}.{body}",
47
- encoding: "base64",
48
- idHeader: "svix-id",
45
+ signatureFormat: 'v1={signature}',
46
+ payloadFormat: '{id}.{timestamp}.{body}',
47
+ encoding: 'base64',
48
+ idHeader: 'svix-id',
49
49
  },
50
50
  },
51
- description: "Clerk webhooks use HMAC-SHA256 with base64 encoding",
51
+ description: 'Clerk webhooks use HMAC-SHA256 with base64 encoding',
52
52
  },
53
53
  dodopayments: {
54
- platform: "dodopayments",
54
+ platform: 'dodopayments',
55
55
  signatureConfig: {
56
- algorithm: "hmac-sha256",
57
- headerName: "webhook-signature",
58
- headerFormat: "raw",
59
- timestampHeader: "webhook-timestamp",
60
- timestampFormat: "unix",
61
- payloadFormat: "custom",
56
+ algorithm: 'hmac-sha256',
57
+ headerName: 'webhook-signature',
58
+ headerFormat: 'raw',
59
+ timestampHeader: 'webhook-timestamp',
60
+ timestampFormat: 'unix',
61
+ payloadFormat: 'custom',
62
62
  customConfig: {
63
- signatureFormat: "v1={signature}",
64
- payloadFormat: "{id}.{timestamp}.{body}",
65
- idHeader: "webhook-id",
63
+ signatureFormat: 'v1={signature}',
64
+ payloadFormat: '{id}.{timestamp}.{body}',
65
+ encoding: 'base64',
66
+ idHeader: 'webhook-id',
66
67
  },
67
68
  },
68
- description: "Dodo Payments webhooks use HMAC-SHA256 with svix-style format (Standard Webhooks)",
69
+ description: 'Dodo Payments webhooks use HMAC-SHA256 with svix-style format (Standard Webhooks)',
69
70
  },
70
71
  shopify: {
71
- platform: "shopify",
72
+ platform: 'shopify',
72
73
  signatureConfig: {
73
- algorithm: "hmac-sha256",
74
- headerName: "x-shopify-hmac-sha256",
75
- headerFormat: "raw",
76
- timestampHeader: "x-shopify-shop-domain",
77
- payloadFormat: "raw",
74
+ algorithm: 'hmac-sha256',
75
+ headerName: 'x-shopify-hmac-sha256',
76
+ headerFormat: 'raw',
77
+ timestampHeader: 'x-shopify-shop-domain',
78
+ payloadFormat: 'raw',
78
79
  },
79
- description: "Shopify webhooks use HMAC-SHA256",
80
+ description: 'Shopify webhooks use HMAC-SHA256',
80
81
  },
81
82
  vercel: {
82
- platform: "vercel",
83
+ platform: 'vercel',
83
84
  signatureConfig: {
84
- algorithm: "hmac-sha256",
85
- headerName: "x-vercel-signature",
86
- headerFormat: "raw",
87
- timestampHeader: "x-vercel-timestamp",
88
- timestampFormat: "unix",
89
- payloadFormat: "raw",
85
+ algorithm: 'hmac-sha256',
86
+ headerName: 'x-vercel-signature',
87
+ headerFormat: 'raw',
88
+ timestampHeader: 'x-vercel-timestamp',
89
+ timestampFormat: 'unix',
90
+ payloadFormat: 'raw',
90
91
  },
91
- description: "Vercel webhooks use HMAC-SHA256",
92
+ description: 'Vercel webhooks use HMAC-SHA256',
92
93
  },
93
94
  polar: {
94
- platform: "polar",
95
+ platform: 'polar',
95
96
  signatureConfig: {
96
- algorithm: "hmac-sha256",
97
- headerName: "x-polar-signature",
98
- headerFormat: "raw",
99
- timestampHeader: "x-polar-timestamp",
100
- timestampFormat: "unix",
101
- payloadFormat: "raw",
97
+ algorithm: 'hmac-sha256',
98
+ headerName: 'x-polar-signature',
99
+ headerFormat: 'raw',
100
+ timestampHeader: 'x-polar-timestamp',
101
+ timestampFormat: 'unix',
102
+ payloadFormat: 'raw',
102
103
  },
103
- description: "Polar webhooks use HMAC-SHA256",
104
+ description: 'Polar webhooks use HMAC-SHA256',
104
105
  },
105
106
  supabase: {
106
- platform: "supabase",
107
+ platform: 'supabase',
107
108
  signatureConfig: {
108
- algorithm: "custom",
109
- headerName: "x-webhook-token",
110
- headerFormat: "raw",
111
- payloadFormat: "raw",
109
+ algorithm: 'custom',
110
+ headerName: 'x-webhook-token',
111
+ headerFormat: 'raw',
112
+ payloadFormat: 'raw',
112
113
  customConfig: {
113
- type: "token-based",
114
- idHeader: "x-webhook-id",
114
+ type: 'token-based',
115
+ idHeader: 'x-webhook-id',
115
116
  },
116
117
  },
117
- description: "Supabase webhooks use token-based authentication",
118
+ description: 'Supabase webhooks use token-based authentication',
118
119
  },
119
120
  custom: {
120
- platform: "custom",
121
+ platform: 'custom',
121
122
  signatureConfig: {
122
- algorithm: "hmac-sha256",
123
- headerName: "x-webhook-token",
124
- headerFormat: "raw",
125
- payloadFormat: "raw",
123
+ algorithm: 'hmac-sha256',
124
+ headerName: 'x-webhook-token',
125
+ headerFormat: 'raw',
126
+ payloadFormat: 'raw',
126
127
  customConfig: {
127
- type: "token-based",
128
- idHeader: "x-webhook-id",
128
+ type: 'token-based',
129
+ idHeader: 'x-webhook-id',
129
130
  },
130
131
  },
131
- description: "Custom webhook configuration",
132
+ description: 'Custom webhook configuration',
132
133
  },
133
134
  unknown: {
134
- platform: "unknown",
135
+ platform: 'unknown',
135
136
  signatureConfig: {
136
- algorithm: "hmac-sha256",
137
- headerName: "x-webhook-signature",
138
- headerFormat: "raw",
139
- payloadFormat: "raw",
137
+ algorithm: 'hmac-sha256',
138
+ headerName: 'x-webhook-signature',
139
+ headerFormat: 'raw',
140
+ payloadFormat: 'raw',
140
141
  },
141
- description: "Unknown platform - using default HMAC-SHA256",
142
+ description: 'Unknown platform - using default HMAC-SHA256',
142
143
  },
143
144
  };
144
145
  function getPlatformAlgorithmConfig(platform) {
@@ -158,14 +159,14 @@ function validateSignatureConfig(config) {
158
159
  return false;
159
160
  }
160
161
  switch (config.algorithm) {
161
- case "hmac-sha256":
162
- case "hmac-sha1":
163
- case "hmac-sha512":
162
+ case 'hmac-sha256':
163
+ case 'hmac-sha1':
164
+ case 'hmac-sha512':
164
165
  return true;
165
- case "rsa-sha256":
166
- case "ed25519":
166
+ case 'rsa-sha256':
167
+ case 'ed25519':
167
168
  return !!config.customConfig?.publicKey;
168
- case "custom":
169
+ case 'custom':
169
170
  return !!config.customConfig;
170
171
  default:
171
172
  return false;
package/dist/test.js CHANGED
@@ -118,18 +118,23 @@ async function runTests() {
118
118
  try {
119
119
  const webhookId = 'test-webhook-id-123';
120
120
  const timestamp = Math.floor(Date.now() / 1000);
121
+ // Create a proper secret format for Standard Webhooks (whsec_ + base64 encoded secret)
122
+ const base64Secret = Buffer.from(testSecret).toString('base64');
123
+ const dodoSecret = `whsec_${base64Secret}`;
121
124
  // Create svix-style signature: {webhook-id}.{webhook-timestamp}.{payload}
122
125
  const signedContent = `${webhookId}.${timestamp}.${testBody}`;
123
- const hmac = (0, crypto_1.createHmac)('sha256', testSecret);
126
+ // Use the base64-decoded secret for HMAC (like the Standard Webhooks library)
127
+ const secretBytes = new Uint8Array(Buffer.from(base64Secret, 'base64'));
128
+ const hmac = (0, crypto_1.createHmac)('sha256', secretBytes);
124
129
  hmac.update(signedContent);
125
- const signature = hmac.digest('hex');
130
+ const signature = `v1,${hmac.digest('base64')}`;
126
131
  const dodoRequest = createMockRequest({
127
132
  'webhook-signature': signature,
128
133
  'webhook-id': webhookId,
129
134
  'webhook-timestamp': timestamp.toString(),
130
135
  'content-type': 'application/json',
131
136
  });
132
- const dodoResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(dodoRequest, 'dodopayments', testSecret);
137
+ const dodoResult = await index_1.WebhookVerificationService.verifyWithPlatformConfig(dodoRequest, 'dodopayments', dodoSecret);
133
138
  console.log(' ✅ Dodo Payments:', dodoResult.isValid ? 'PASSED' : 'FAILED');
134
139
  if (!dodoResult.isValid) {
135
140
  console.log(' ❌ Error:', dodoResult.error);
@@ -1,5 +1,5 @@
1
- import { WebhookVerifier } from "./base";
2
- import { WebhookVerificationResult, SignatureConfig, WebhookPlatform } from "../types";
1
+ import { WebhookVerifier } from './base';
2
+ import { WebhookVerificationResult, SignatureConfig, WebhookPlatform } from '../types';
3
3
  export declare abstract class AlgorithmBasedVerifier extends WebhookVerifier {
4
4
  protected config: SignatureConfig;
5
5
  protected platform: WebhookPlatform;