@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
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from
|
|
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:
|
|
10
|
+
platform: 'github',
|
|
11
11
|
signatureConfig: {
|
|
12
|
-
algorithm:
|
|
13
|
-
headerName:
|
|
14
|
-
headerFormat:
|
|
15
|
-
prefix:
|
|
12
|
+
algorithm: 'hmac-sha256',
|
|
13
|
+
headerName: 'x-hub-signature-256',
|
|
14
|
+
headerFormat: 'prefixed',
|
|
15
|
+
prefix: 'sha256=',
|
|
16
16
|
timestampHeader: undefined,
|
|
17
|
-
payloadFormat:
|
|
17
|
+
payloadFormat: 'raw',
|
|
18
18
|
},
|
|
19
|
-
description:
|
|
19
|
+
description: 'GitHub webhooks use HMAC-SHA256 with sha256= prefix',
|
|
20
20
|
},
|
|
21
21
|
stripe: {
|
|
22
|
-
platform:
|
|
22
|
+
platform: 'stripe',
|
|
23
23
|
signatureConfig: {
|
|
24
|
-
algorithm:
|
|
25
|
-
headerName:
|
|
26
|
-
headerFormat:
|
|
24
|
+
algorithm: 'hmac-sha256',
|
|
25
|
+
headerName: 'stripe-signature',
|
|
26
|
+
headerFormat: 'comma-separated',
|
|
27
27
|
timestampHeader: undefined,
|
|
28
|
-
payloadFormat:
|
|
28
|
+
payloadFormat: 'timestamped',
|
|
29
29
|
customConfig: {
|
|
30
|
-
signatureFormat:
|
|
30
|
+
signatureFormat: 't={timestamp},v1={signature}',
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
|
-
description:
|
|
33
|
+
description: 'Stripe webhooks use HMAC-SHA256 with comma-separated format',
|
|
34
34
|
},
|
|
35
35
|
clerk: {
|
|
36
|
-
platform:
|
|
36
|
+
platform: 'clerk',
|
|
37
37
|
signatureConfig: {
|
|
38
|
-
algorithm:
|
|
39
|
-
headerName:
|
|
40
|
-
headerFormat:
|
|
41
|
-
timestampHeader:
|
|
42
|
-
timestampFormat:
|
|
43
|
-
payloadFormat:
|
|
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:
|
|
46
|
-
payloadFormat:
|
|
47
|
-
encoding:
|
|
48
|
-
idHeader:
|
|
45
|
+
signatureFormat: 'v1={signature}',
|
|
46
|
+
payloadFormat: '{id}.{timestamp}.{body}',
|
|
47
|
+
encoding: 'base64',
|
|
48
|
+
idHeader: 'svix-id',
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
|
-
description:
|
|
51
|
+
description: 'Clerk webhooks use HMAC-SHA256 with base64 encoding',
|
|
52
52
|
},
|
|
53
53
|
dodopayments: {
|
|
54
|
-
platform:
|
|
54
|
+
platform: 'dodopayments',
|
|
55
55
|
signatureConfig: {
|
|
56
|
-
algorithm:
|
|
57
|
-
headerName:
|
|
58
|
-
headerFormat:
|
|
59
|
-
timestampHeader:
|
|
60
|
-
timestampFormat:
|
|
61
|
-
payloadFormat:
|
|
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:
|
|
64
|
-
payloadFormat:
|
|
65
|
-
|
|
63
|
+
signatureFormat: 'v1={signature}',
|
|
64
|
+
payloadFormat: '{id}.{timestamp}.{body}',
|
|
65
|
+
encoding: 'base64',
|
|
66
|
+
idHeader: 'webhook-id',
|
|
66
67
|
},
|
|
67
68
|
},
|
|
68
|
-
description:
|
|
69
|
+
description: 'Dodo Payments webhooks use HMAC-SHA256 with svix-style format (Standard Webhooks)',
|
|
69
70
|
},
|
|
70
71
|
shopify: {
|
|
71
|
-
platform:
|
|
72
|
+
platform: 'shopify',
|
|
72
73
|
signatureConfig: {
|
|
73
|
-
algorithm:
|
|
74
|
-
headerName:
|
|
75
|
-
headerFormat:
|
|
76
|
-
timestampHeader:
|
|
77
|
-
payloadFormat:
|
|
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:
|
|
80
|
+
description: 'Shopify webhooks use HMAC-SHA256',
|
|
80
81
|
},
|
|
81
82
|
vercel: {
|
|
82
|
-
platform:
|
|
83
|
+
platform: 'vercel',
|
|
83
84
|
signatureConfig: {
|
|
84
|
-
algorithm:
|
|
85
|
-
headerName:
|
|
86
|
-
headerFormat:
|
|
87
|
-
timestampHeader:
|
|
88
|
-
timestampFormat:
|
|
89
|
-
payloadFormat:
|
|
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:
|
|
92
|
+
description: 'Vercel webhooks use HMAC-SHA256',
|
|
92
93
|
},
|
|
93
94
|
polar: {
|
|
94
|
-
platform:
|
|
95
|
+
platform: 'polar',
|
|
95
96
|
signatureConfig: {
|
|
96
|
-
algorithm:
|
|
97
|
-
headerName:
|
|
98
|
-
headerFormat:
|
|
99
|
-
timestampHeader:
|
|
100
|
-
timestampFormat:
|
|
101
|
-
payloadFormat:
|
|
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:
|
|
104
|
+
description: 'Polar webhooks use HMAC-SHA256',
|
|
104
105
|
},
|
|
105
106
|
supabase: {
|
|
106
|
-
platform:
|
|
107
|
+
platform: 'supabase',
|
|
107
108
|
signatureConfig: {
|
|
108
|
-
algorithm:
|
|
109
|
-
headerName:
|
|
110
|
-
headerFormat:
|
|
111
|
-
payloadFormat:
|
|
109
|
+
algorithm: 'custom',
|
|
110
|
+
headerName: 'x-webhook-token',
|
|
111
|
+
headerFormat: 'raw',
|
|
112
|
+
payloadFormat: 'raw',
|
|
112
113
|
customConfig: {
|
|
113
|
-
type:
|
|
114
|
-
idHeader:
|
|
114
|
+
type: 'token-based',
|
|
115
|
+
idHeader: 'x-webhook-id',
|
|
115
116
|
},
|
|
116
117
|
},
|
|
117
|
-
description:
|
|
118
|
+
description: 'Supabase webhooks use token-based authentication',
|
|
118
119
|
},
|
|
119
120
|
custom: {
|
|
120
|
-
platform:
|
|
121
|
+
platform: 'custom',
|
|
121
122
|
signatureConfig: {
|
|
122
|
-
algorithm:
|
|
123
|
-
headerName:
|
|
124
|
-
headerFormat:
|
|
125
|
-
payloadFormat:
|
|
123
|
+
algorithm: 'hmac-sha256',
|
|
124
|
+
headerName: 'x-webhook-token',
|
|
125
|
+
headerFormat: 'raw',
|
|
126
|
+
payloadFormat: 'raw',
|
|
126
127
|
customConfig: {
|
|
127
|
-
type:
|
|
128
|
-
idHeader:
|
|
128
|
+
type: 'token-based',
|
|
129
|
+
idHeader: 'x-webhook-id',
|
|
129
130
|
},
|
|
130
131
|
},
|
|
131
|
-
description:
|
|
132
|
+
description: 'Custom webhook configuration',
|
|
132
133
|
},
|
|
133
134
|
unknown: {
|
|
134
|
-
platform:
|
|
135
|
+
platform: 'unknown',
|
|
135
136
|
signatureConfig: {
|
|
136
|
-
algorithm:
|
|
137
|
-
headerName:
|
|
138
|
-
headerFormat:
|
|
139
|
-
payloadFormat:
|
|
137
|
+
algorithm: 'hmac-sha256',
|
|
138
|
+
headerName: 'x-webhook-signature',
|
|
139
|
+
headerFormat: 'raw',
|
|
140
|
+
payloadFormat: 'raw',
|
|
140
141
|
},
|
|
141
|
-
description:
|
|
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
|
|
162
|
-
case
|
|
163
|
-
case
|
|
162
|
+
case 'hmac-sha256':
|
|
163
|
+
case 'hmac-sha1':
|
|
164
|
+
case 'hmac-sha512':
|
|
164
165
|
return true;
|
|
165
|
-
case
|
|
166
|
-
case
|
|
166
|
+
case 'rsa-sha256':
|
|
167
|
+
case 'ed25519':
|
|
167
168
|
return !!config.customConfig?.publicKey;
|
|
168
|
-
case
|
|
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
|
-
|
|
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('
|
|
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',
|
|
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
|
|
2
|
-
import { WebhookVerificationResult, SignatureConfig, WebhookPlatform } from
|
|
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;
|