@hookflo/tern 1.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/LICENSE +21 -0
- package/README.md +396 -0
- package/dist/examples.d.ts +28 -0
- package/dist/examples.js +160 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +142 -0
- package/dist/platforms/algorithms.d.ts +6 -0
- package/dist/platforms/algorithms.js +180 -0
- package/dist/test-compiled.d.ts +1 -0
- package/dist/test-compiled.js +4 -0
- package/dist/test.d.ts +6 -0
- package/dist/test.js +102 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +16 -0
- package/dist/utils.d.ts +78 -0
- package/dist/utils.js +212 -0
- package/dist/verifiers/algorithms.d.ts +22 -0
- package/dist/verifiers/algorithms.js +273 -0
- package/dist/verifiers/base.d.ts +9 -0
- package/dist/verifiers/base.js +21 -0
- package/dist/verifiers/custom-algorithms.d.ts +18 -0
- package/dist/verifiers/custom-algorithms.js +279 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.createCustomVerifier = exports.createAlgorithmVerifier = exports.validateSignatureConfig = exports.getPlatformsUsingAlgorithm = exports.platformUsesAlgorithm = exports.getPlatformAlgorithmConfig = exports.WebhookVerificationService = void 0;
|
|
18
|
+
const algorithms_1 = require("./verifiers/algorithms");
|
|
19
|
+
const custom_algorithms_1 = require("./verifiers/custom-algorithms");
|
|
20
|
+
const algorithms_2 = require("./platforms/algorithms");
|
|
21
|
+
class WebhookVerificationService {
|
|
22
|
+
static async verify(request, config) {
|
|
23
|
+
const verifier = this.getVerifier(config);
|
|
24
|
+
return await verifier.verify(request);
|
|
25
|
+
}
|
|
26
|
+
static getVerifier(config) {
|
|
27
|
+
const platform = config.platform.toLowerCase();
|
|
28
|
+
// If a custom signature config is provided, use the new algorithm-based framework
|
|
29
|
+
if (config.signatureConfig) {
|
|
30
|
+
return this.createAlgorithmBasedVerifier(config);
|
|
31
|
+
}
|
|
32
|
+
// Fallback to platform-specific verifiers for backward compatibility
|
|
33
|
+
return this.getLegacyVerifier(config);
|
|
34
|
+
}
|
|
35
|
+
static createAlgorithmBasedVerifier(config) {
|
|
36
|
+
const { signatureConfig, secret, toleranceInSeconds = 300 } = config;
|
|
37
|
+
if (!signatureConfig) {
|
|
38
|
+
throw new Error('Signature config is required for algorithm-based verification');
|
|
39
|
+
}
|
|
40
|
+
// Use custom verifiers for special cases
|
|
41
|
+
if (signatureConfig.algorithm === 'custom') {
|
|
42
|
+
return (0, custom_algorithms_1.createCustomVerifier)(secret, signatureConfig, toleranceInSeconds);
|
|
43
|
+
}
|
|
44
|
+
// Use algorithm-based verifiers for standard algorithms
|
|
45
|
+
return (0, algorithms_1.createAlgorithmVerifier)(secret, signatureConfig, toleranceInSeconds);
|
|
46
|
+
}
|
|
47
|
+
static getLegacyVerifier(config) {
|
|
48
|
+
const platform = config.platform.toLowerCase();
|
|
49
|
+
// For legacy support, we'll use the algorithm-based approach
|
|
50
|
+
const platformConfig = (0, algorithms_2.getPlatformAlgorithmConfig)(platform);
|
|
51
|
+
const configWithSignature = {
|
|
52
|
+
...config,
|
|
53
|
+
signatureConfig: platformConfig.signatureConfig,
|
|
54
|
+
};
|
|
55
|
+
return this.createAlgorithmBasedVerifier(configWithSignature);
|
|
56
|
+
}
|
|
57
|
+
// New method to create verifier using platform algorithm config
|
|
58
|
+
static async verifyWithPlatformConfig(request, platform, secret, toleranceInSeconds = 300) {
|
|
59
|
+
const platformConfig = (0, algorithms_2.getPlatformAlgorithmConfig)(platform);
|
|
60
|
+
const config = {
|
|
61
|
+
platform,
|
|
62
|
+
secret,
|
|
63
|
+
toleranceInSeconds,
|
|
64
|
+
signatureConfig: platformConfig.signatureConfig,
|
|
65
|
+
};
|
|
66
|
+
return await this.verify(request, config);
|
|
67
|
+
}
|
|
68
|
+
// Helper method to get all platforms using a specific algorithm
|
|
69
|
+
static getPlatformsUsingAlgorithm(algorithm) {
|
|
70
|
+
const { getPlatformsUsingAlgorithm } = require('./platforms/algorithms');
|
|
71
|
+
return getPlatformsUsingAlgorithm(algorithm);
|
|
72
|
+
}
|
|
73
|
+
// Helper method to check if a platform uses a specific algorithm
|
|
74
|
+
static platformUsesAlgorithm(platform, algorithm) {
|
|
75
|
+
const { platformUsesAlgorithm } = require('./platforms/algorithms');
|
|
76
|
+
return platformUsesAlgorithm(platform, algorithm);
|
|
77
|
+
}
|
|
78
|
+
// Helper method to validate signature config
|
|
79
|
+
static validateSignatureConfig(config) {
|
|
80
|
+
const { validateSignatureConfig } = require('./platforms/algorithms');
|
|
81
|
+
return validateSignatureConfig(config);
|
|
82
|
+
}
|
|
83
|
+
// Simple token-based verification for platforms like Supabase
|
|
84
|
+
static async verifyTokenBased(request, webhookId, webhookToken) {
|
|
85
|
+
try {
|
|
86
|
+
const idHeader = request.headers.get('x-webhook-id');
|
|
87
|
+
const tokenHeader = request.headers.get('x-webhook-token');
|
|
88
|
+
if (!idHeader || !tokenHeader) {
|
|
89
|
+
return {
|
|
90
|
+
isValid: false,
|
|
91
|
+
error: 'Missing required headers: x-webhook-id and x-webhook-token',
|
|
92
|
+
platform: 'custom',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Simple comparison - we don't actually verify, just check if tokens match
|
|
96
|
+
const isValid = idHeader === webhookId && tokenHeader === webhookToken;
|
|
97
|
+
if (!isValid) {
|
|
98
|
+
return {
|
|
99
|
+
isValid: false,
|
|
100
|
+
error: 'Invalid webhook ID or token',
|
|
101
|
+
platform: 'custom',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const rawBody = await request.text();
|
|
105
|
+
let payload;
|
|
106
|
+
try {
|
|
107
|
+
payload = JSON.parse(rawBody);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
payload = rawBody;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
isValid: true,
|
|
114
|
+
platform: 'custom',
|
|
115
|
+
payload,
|
|
116
|
+
metadata: {
|
|
117
|
+
id: idHeader,
|
|
118
|
+
algorithm: 'token-based',
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
isValid: false,
|
|
125
|
+
error: `Token-based verification error: ${error.message}`,
|
|
126
|
+
platform: 'custom',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.WebhookVerificationService = WebhookVerificationService;
|
|
132
|
+
__exportStar(require("./types"), exports);
|
|
133
|
+
var algorithms_3 = require("./platforms/algorithms");
|
|
134
|
+
Object.defineProperty(exports, "getPlatformAlgorithmConfig", { enumerable: true, get: function () { return algorithms_3.getPlatformAlgorithmConfig; } });
|
|
135
|
+
Object.defineProperty(exports, "platformUsesAlgorithm", { enumerable: true, get: function () { return algorithms_3.platformUsesAlgorithm; } });
|
|
136
|
+
Object.defineProperty(exports, "getPlatformsUsingAlgorithm", { enumerable: true, get: function () { return algorithms_3.getPlatformsUsingAlgorithm; } });
|
|
137
|
+
Object.defineProperty(exports, "validateSignatureConfig", { enumerable: true, get: function () { return algorithms_3.validateSignatureConfig; } });
|
|
138
|
+
var algorithms_4 = require("./verifiers/algorithms");
|
|
139
|
+
Object.defineProperty(exports, "createAlgorithmVerifier", { enumerable: true, get: function () { return algorithms_4.createAlgorithmVerifier; } });
|
|
140
|
+
var custom_algorithms_2 = require("./verifiers/custom-algorithms");
|
|
141
|
+
Object.defineProperty(exports, "createCustomVerifier", { enumerable: true, get: function () { return custom_algorithms_2.createCustomVerifier; } });
|
|
142
|
+
exports.default = WebhookVerificationService;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { PlatformAlgorithmConfig, WebhookPlatform, SignatureConfig } from "../types";
|
|
2
|
+
export declare const platformAlgorithmConfigs: Record<WebhookPlatform, PlatformAlgorithmConfig>;
|
|
3
|
+
export declare function getPlatformAlgorithmConfig(platform: WebhookPlatform): PlatformAlgorithmConfig;
|
|
4
|
+
export declare function platformUsesAlgorithm(platform: WebhookPlatform, algorithm: string): boolean;
|
|
5
|
+
export declare function getPlatformsUsingAlgorithm(algorithm: string): WebhookPlatform[];
|
|
6
|
+
export declare function validateSignatureConfig(config: SignatureConfig): boolean;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.platformAlgorithmConfigs = void 0;
|
|
4
|
+
exports.getPlatformAlgorithmConfig = getPlatformAlgorithmConfig;
|
|
5
|
+
exports.platformUsesAlgorithm = platformUsesAlgorithm;
|
|
6
|
+
exports.getPlatformsUsingAlgorithm = getPlatformsUsingAlgorithm;
|
|
7
|
+
exports.validateSignatureConfig = validateSignatureConfig;
|
|
8
|
+
// Platform to algorithm mapping configuration
|
|
9
|
+
exports.platformAlgorithmConfigs = {
|
|
10
|
+
// GitHub uses HMAC-SHA256 with prefixed signature
|
|
11
|
+
github: {
|
|
12
|
+
platform: "github",
|
|
13
|
+
signatureConfig: {
|
|
14
|
+
algorithm: "hmac-sha256",
|
|
15
|
+
headerName: "x-hub-signature-256",
|
|
16
|
+
headerFormat: "prefixed",
|
|
17
|
+
prefix: "sha256=",
|
|
18
|
+
timestampHeader: undefined, // GitHub doesn't use timestamp validation
|
|
19
|
+
payloadFormat: "raw",
|
|
20
|
+
},
|
|
21
|
+
description: "GitHub webhooks use HMAC-SHA256 with sha256= prefix",
|
|
22
|
+
},
|
|
23
|
+
// Stripe uses HMAC-SHA256 with comma-separated format
|
|
24
|
+
stripe: {
|
|
25
|
+
platform: "stripe",
|
|
26
|
+
signatureConfig: {
|
|
27
|
+
algorithm: "hmac-sha256",
|
|
28
|
+
headerName: "stripe-signature",
|
|
29
|
+
headerFormat: "comma-separated",
|
|
30
|
+
timestampHeader: undefined, // Timestamp is embedded in signature
|
|
31
|
+
payloadFormat: "timestamped",
|
|
32
|
+
customConfig: {
|
|
33
|
+
signatureFormat: "t={timestamp},v1={signature}",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
description: "Stripe webhooks use HMAC-SHA256 with comma-separated format",
|
|
37
|
+
},
|
|
38
|
+
// Clerk uses HMAC-SHA256 with custom base64 encoding
|
|
39
|
+
clerk: {
|
|
40
|
+
platform: "clerk",
|
|
41
|
+
signatureConfig: {
|
|
42
|
+
algorithm: "custom",
|
|
43
|
+
headerName: "svix-signature",
|
|
44
|
+
headerFormat: "raw",
|
|
45
|
+
timestampHeader: "svix-timestamp",
|
|
46
|
+
timestampFormat: "unix",
|
|
47
|
+
payloadFormat: "custom",
|
|
48
|
+
customConfig: {
|
|
49
|
+
type: "clerk-custom",
|
|
50
|
+
signatureFormat: "v1={signature}",
|
|
51
|
+
payloadFormat: "{id}.{timestamp}.{body}",
|
|
52
|
+
encoding: "base64",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
description: "Clerk webhooks use HMAC-SHA256 with base64 encoding",
|
|
56
|
+
},
|
|
57
|
+
// Dodo Payments uses HMAC-SHA256
|
|
58
|
+
dodopayments: {
|
|
59
|
+
platform: "dodopayments",
|
|
60
|
+
signatureConfig: {
|
|
61
|
+
algorithm: "hmac-sha256",
|
|
62
|
+
headerName: "webhook-signature",
|
|
63
|
+
headerFormat: "raw",
|
|
64
|
+
timestampHeader: "webhook-timestamp",
|
|
65
|
+
timestampFormat: "unix",
|
|
66
|
+
payloadFormat: "raw",
|
|
67
|
+
},
|
|
68
|
+
description: "Dodo Payments webhooks use HMAC-SHA256",
|
|
69
|
+
},
|
|
70
|
+
// Shopify uses HMAC-SHA256
|
|
71
|
+
shopify: {
|
|
72
|
+
platform: "shopify",
|
|
73
|
+
signatureConfig: {
|
|
74
|
+
algorithm: "hmac-sha256",
|
|
75
|
+
headerName: "x-shopify-hmac-sha256",
|
|
76
|
+
headerFormat: "raw",
|
|
77
|
+
timestampHeader: "x-shopify-shop-domain",
|
|
78
|
+
payloadFormat: "raw",
|
|
79
|
+
},
|
|
80
|
+
description: "Shopify webhooks use HMAC-SHA256",
|
|
81
|
+
},
|
|
82
|
+
// Vercel uses HMAC-SHA256
|
|
83
|
+
vercel: {
|
|
84
|
+
platform: "vercel",
|
|
85
|
+
signatureConfig: {
|
|
86
|
+
algorithm: "hmac-sha256",
|
|
87
|
+
headerName: "x-vercel-signature",
|
|
88
|
+
headerFormat: "raw",
|
|
89
|
+
timestampHeader: "x-vercel-timestamp",
|
|
90
|
+
timestampFormat: "unix",
|
|
91
|
+
payloadFormat: "raw",
|
|
92
|
+
},
|
|
93
|
+
description: "Vercel webhooks use HMAC-SHA256",
|
|
94
|
+
},
|
|
95
|
+
// Polar uses HMAC-SHA256
|
|
96
|
+
polar: {
|
|
97
|
+
platform: "polar",
|
|
98
|
+
signatureConfig: {
|
|
99
|
+
algorithm: "hmac-sha256",
|
|
100
|
+
headerName: "x-polar-signature",
|
|
101
|
+
headerFormat: "raw",
|
|
102
|
+
timestampHeader: "x-polar-timestamp",
|
|
103
|
+
timestampFormat: "unix",
|
|
104
|
+
payloadFormat: "raw",
|
|
105
|
+
},
|
|
106
|
+
description: "Polar webhooks use HMAC-SHA256",
|
|
107
|
+
},
|
|
108
|
+
// Supabase uses simple token-based authentication
|
|
109
|
+
supabase: {
|
|
110
|
+
platform: "supabase",
|
|
111
|
+
signatureConfig: {
|
|
112
|
+
algorithm: "custom",
|
|
113
|
+
headerName: "x-webhook-token",
|
|
114
|
+
headerFormat: "raw",
|
|
115
|
+
payloadFormat: "raw",
|
|
116
|
+
customConfig: {
|
|
117
|
+
type: "token-based",
|
|
118
|
+
idHeader: "x-webhook-id",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
description: "Supabase webhooks use token-based authentication",
|
|
122
|
+
},
|
|
123
|
+
// Custom platform - can be configured per instance
|
|
124
|
+
custom: {
|
|
125
|
+
platform: "custom",
|
|
126
|
+
signatureConfig: {
|
|
127
|
+
algorithm: "hmac-sha256",
|
|
128
|
+
headerName: "x-webhook-token",
|
|
129
|
+
headerFormat: "raw",
|
|
130
|
+
payloadFormat: "raw",
|
|
131
|
+
},
|
|
132
|
+
description: "Custom webhook configuration",
|
|
133
|
+
},
|
|
134
|
+
// Unknown platform - fallback
|
|
135
|
+
unknown: {
|
|
136
|
+
platform: "unknown",
|
|
137
|
+
signatureConfig: {
|
|
138
|
+
algorithm: "hmac-sha256",
|
|
139
|
+
headerName: "x-webhook-signature",
|
|
140
|
+
headerFormat: "raw",
|
|
141
|
+
payloadFormat: "raw",
|
|
142
|
+
},
|
|
143
|
+
description: "Unknown platform - using default HMAC-SHA256",
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
// Helper function to get algorithm config for a platform
|
|
147
|
+
function getPlatformAlgorithmConfig(platform) {
|
|
148
|
+
return exports.platformAlgorithmConfigs[platform] || exports.platformAlgorithmConfigs.unknown;
|
|
149
|
+
}
|
|
150
|
+
// Helper function to check if a platform uses a specific algorithm
|
|
151
|
+
function platformUsesAlgorithm(platform, algorithm) {
|
|
152
|
+
const config = getPlatformAlgorithmConfig(platform);
|
|
153
|
+
return config.signatureConfig.algorithm === algorithm;
|
|
154
|
+
}
|
|
155
|
+
// Helper function to get all platforms using a specific algorithm
|
|
156
|
+
function getPlatformsUsingAlgorithm(algorithm) {
|
|
157
|
+
return Object.entries(exports.platformAlgorithmConfigs)
|
|
158
|
+
.filter(([_, config]) => config.signatureConfig.algorithm === algorithm)
|
|
159
|
+
.map(([platform, _]) => platform);
|
|
160
|
+
}
|
|
161
|
+
// Helper function to validate signature config
|
|
162
|
+
function validateSignatureConfig(config) {
|
|
163
|
+
if (!config.algorithm || !config.headerName) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
// Validate algorithm-specific requirements
|
|
167
|
+
switch (config.algorithm) {
|
|
168
|
+
case "hmac-sha256":
|
|
169
|
+
case "hmac-sha1":
|
|
170
|
+
case "hmac-sha512":
|
|
171
|
+
return true; // These algorithms only need headerName
|
|
172
|
+
case "rsa-sha256":
|
|
173
|
+
case "ed25519":
|
|
174
|
+
return !!config.customConfig?.publicKey; // These need public key
|
|
175
|
+
case "custom":
|
|
176
|
+
return !!config.customConfig; // Custom needs custom config
|
|
177
|
+
default:
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare function testGitHubWebhook(): Promise<import("./types").WebhookVerificationResult>;
|
|
2
|
+
declare function testTokenBasedWebhook(): Promise<import("./types").WebhookVerificationResult>;
|
|
3
|
+
declare function testPlatformDetection(): void;
|
|
4
|
+
declare function testCustomSignature(): Promise<import("./types").WebhookVerificationResult>;
|
|
5
|
+
export declare function runAllTests(): Promise<void>;
|
|
6
|
+
export { testGitHubWebhook, testTokenBasedWebhook, testPlatformDetection, testCustomSignature, };
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAllTests = runAllTests;
|
|
4
|
+
exports.testGitHubWebhook = testGitHubWebhook;
|
|
5
|
+
exports.testTokenBasedWebhook = testTokenBasedWebhook;
|
|
6
|
+
exports.testPlatformDetection = testPlatformDetection;
|
|
7
|
+
exports.testCustomSignature = testCustomSignature;
|
|
8
|
+
const index_1 = require("./index");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
// Mock request for testing
|
|
11
|
+
function createMockRequest(headers, body = '{"test": "data"}') {
|
|
12
|
+
return new Request('http://localhost/webhook', {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers,
|
|
15
|
+
body,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// Test GitHub webhook
|
|
19
|
+
async function testGitHubWebhook() {
|
|
20
|
+
console.log('Testing GitHub webhook...');
|
|
21
|
+
const request = createMockRequest({
|
|
22
|
+
'x-hub-signature-256': 'sha256=abc123',
|
|
23
|
+
'content-type': 'application/json',
|
|
24
|
+
});
|
|
25
|
+
const result = await index_1.WebhookVerificationService.verifyWithPlatformConfig(request, 'github', 'test-secret', 300);
|
|
26
|
+
console.log('GitHub result:', result);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
// Test token-based webhook (Supabase style)
|
|
30
|
+
async function testTokenBasedWebhook() {
|
|
31
|
+
console.log('Testing token-based webhook...');
|
|
32
|
+
const request = createMockRequest({
|
|
33
|
+
'x-webhook-id': 'test-webhook-id',
|
|
34
|
+
'x-webhook-token': 'test-webhook-token',
|
|
35
|
+
'content-type': 'application/json',
|
|
36
|
+
});
|
|
37
|
+
const result = await index_1.WebhookVerificationService.verifyTokenBased(request, 'test-webhook-id', 'test-webhook-token');
|
|
38
|
+
console.log('Token-based result:', result);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
// Test platform detection
|
|
42
|
+
function testPlatformDetection() {
|
|
43
|
+
console.log('Testing platform detection...');
|
|
44
|
+
const testCases = [
|
|
45
|
+
{
|
|
46
|
+
name: 'GitHub',
|
|
47
|
+
headers: { 'x-hub-signature-256': 'sha256=abc123' },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Stripe',
|
|
51
|
+
headers: { 'stripe-signature': 't=1234567890,v1=abc123' },
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Clerk',
|
|
55
|
+
headers: { 'svix-signature': 'v1,abc123' },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Supabase',
|
|
59
|
+
headers: { 'x-webhook-token': 'token123' },
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
for (const testCase of testCases) {
|
|
63
|
+
const request = createMockRequest((0, utils_1.cleanHeaders)(testCase.headers));
|
|
64
|
+
const detectedPlatform = (0, utils_1.detectPlatformFromHeaders)(request.headers);
|
|
65
|
+
console.log(`${testCase.name}: ${detectedPlatform}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Test custom signature configuration
|
|
69
|
+
async function testCustomSignature() {
|
|
70
|
+
console.log('Testing custom signature...');
|
|
71
|
+
const request = createMockRequest({
|
|
72
|
+
'x-custom-signature': 'sha256=abc123',
|
|
73
|
+
'content-type': 'application/json',
|
|
74
|
+
});
|
|
75
|
+
const result = await index_1.WebhookVerificationService.verify(request, {
|
|
76
|
+
platform: 'custom',
|
|
77
|
+
secret: 'custom-secret',
|
|
78
|
+
signatureConfig: {
|
|
79
|
+
algorithm: 'hmac-sha256',
|
|
80
|
+
headerName: 'x-custom-signature',
|
|
81
|
+
headerFormat: 'prefixed',
|
|
82
|
+
prefix: 'sha256=',
|
|
83
|
+
payloadFormat: 'raw',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
console.log('Custom signature result:', result);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
// Run all tests
|
|
90
|
+
async function runAllTests() {
|
|
91
|
+
console.log('🚀 Running Webhook Verifier Tests\n');
|
|
92
|
+
try {
|
|
93
|
+
await testGitHubWebhook();
|
|
94
|
+
await testTokenBasedWebhook();
|
|
95
|
+
testPlatformDetection();
|
|
96
|
+
await testCustomSignature();
|
|
97
|
+
console.log('\n✅ All tests completed successfully!');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error('\n❌ Test failed:', error);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type WebhookPlatform = 'custom' | 'clerk' | 'supabase' | 'github' | 'stripe' | 'shopify' | 'vercel' | 'polar' | 'dodopayments' | 'unknown';
|
|
2
|
+
export declare enum WebhookPlatformKeys {
|
|
3
|
+
GitHub = "github",
|
|
4
|
+
Stripe = "stripe",
|
|
5
|
+
Clerk = "clerk",
|
|
6
|
+
DodoPayments = "dodopayments",
|
|
7
|
+
Shopify = "shopify",
|
|
8
|
+
Vercel = "vercel",
|
|
9
|
+
Polar = "polar",
|
|
10
|
+
Supabase = "supabase",
|
|
11
|
+
Custom = "custom",
|
|
12
|
+
Unknown = "unknown"
|
|
13
|
+
}
|
|
14
|
+
export type SignatureAlgorithm = 'hmac-sha256' | 'hmac-sha1' | 'hmac-sha512' | 'rsa-sha256' | 'ed25519' | 'custom';
|
|
15
|
+
export interface SignatureConfig {
|
|
16
|
+
algorithm: SignatureAlgorithm;
|
|
17
|
+
headerName: string;
|
|
18
|
+
headerFormat?: 'raw' | 'prefixed' | 'comma-separated';
|
|
19
|
+
prefix?: string;
|
|
20
|
+
timestampHeader?: string;
|
|
21
|
+
timestampFormat?: 'unix' | 'iso' | 'custom';
|
|
22
|
+
payloadFormat?: 'raw' | 'timestamped' | 'custom';
|
|
23
|
+
customConfig?: Record<string, any>;
|
|
24
|
+
}
|
|
25
|
+
export interface WebhookVerificationResult {
|
|
26
|
+
isValid: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
platform: WebhookPlatform;
|
|
29
|
+
payload?: any;
|
|
30
|
+
metadata?: {
|
|
31
|
+
timestamp?: string;
|
|
32
|
+
id?: string | null;
|
|
33
|
+
[key: string]: any;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface WebhookConfig {
|
|
37
|
+
platform: WebhookPlatform;
|
|
38
|
+
secret: string;
|
|
39
|
+
toleranceInSeconds?: number;
|
|
40
|
+
signatureConfig?: SignatureConfig;
|
|
41
|
+
}
|
|
42
|
+
export interface PlatformAlgorithmConfig {
|
|
43
|
+
platform: WebhookPlatform;
|
|
44
|
+
signatureConfig: SignatureConfig;
|
|
45
|
+
description?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface TokenAuthConfig {
|
|
48
|
+
webhookId: string;
|
|
49
|
+
webhookToken: string;
|
|
50
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookPlatformKeys = void 0;
|
|
4
|
+
var WebhookPlatformKeys;
|
|
5
|
+
(function (WebhookPlatformKeys) {
|
|
6
|
+
WebhookPlatformKeys["GitHub"] = "github";
|
|
7
|
+
WebhookPlatformKeys["Stripe"] = "stripe";
|
|
8
|
+
WebhookPlatformKeys["Clerk"] = "clerk";
|
|
9
|
+
WebhookPlatformKeys["DodoPayments"] = "dodopayments";
|
|
10
|
+
WebhookPlatformKeys["Shopify"] = "shopify";
|
|
11
|
+
WebhookPlatformKeys["Vercel"] = "vercel";
|
|
12
|
+
WebhookPlatformKeys["Polar"] = "polar";
|
|
13
|
+
WebhookPlatformKeys["Supabase"] = "supabase";
|
|
14
|
+
WebhookPlatformKeys["Custom"] = "custom";
|
|
15
|
+
WebhookPlatformKeys["Unknown"] = "unknown";
|
|
16
|
+
})(WebhookPlatformKeys || (exports.WebhookPlatformKeys = WebhookPlatformKeys = {}));
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { WebhookPlatform, SignatureConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Utility functions for the scalable webhook verification framework
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Get the recommended algorithm for a platform
|
|
7
|
+
*/
|
|
8
|
+
export declare function getRecommendedAlgorithm(platform: WebhookPlatform): string;
|
|
9
|
+
/**
|
|
10
|
+
* Check if a platform supports a specific algorithm
|
|
11
|
+
*/
|
|
12
|
+
export declare function platformSupportsAlgorithm(platform: WebhookPlatform, algorithm: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Get all platforms that support a specific algorithm
|
|
15
|
+
*/
|
|
16
|
+
export declare function getPlatformsByAlgorithm(algorithm: string): WebhookPlatform[];
|
|
17
|
+
/**
|
|
18
|
+
* Create a signature config for a platform
|
|
19
|
+
*/
|
|
20
|
+
export declare function createSignatureConfigForPlatform(platform: WebhookPlatform): SignatureConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Validate a signature config
|
|
23
|
+
*/
|
|
24
|
+
export declare function isValidSignatureConfig(config: SignatureConfig): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Get platform description
|
|
27
|
+
*/
|
|
28
|
+
export declare function getPlatformDescription(platform: WebhookPlatform): string;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a platform uses custom algorithm
|
|
31
|
+
*/
|
|
32
|
+
export declare function isCustomAlgorithm(platform: WebhookPlatform): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Get algorithm statistics
|
|
35
|
+
*/
|
|
36
|
+
export declare function getAlgorithmStats(): Record<string, number>;
|
|
37
|
+
/**
|
|
38
|
+
* Get most common algorithm
|
|
39
|
+
*/
|
|
40
|
+
export declare function getMostCommonAlgorithm(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Check if a request matches a platform's signature pattern
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectPlatformFromHeaders(headers: Headers): WebhookPlatform | null;
|
|
45
|
+
/**
|
|
46
|
+
* Get platform configuration summary
|
|
47
|
+
*/
|
|
48
|
+
export declare function getPlatformSummary(): Array<{
|
|
49
|
+
platform: WebhookPlatform;
|
|
50
|
+
algorithm: string;
|
|
51
|
+
description: string;
|
|
52
|
+
isCustom: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Compare two signature configs
|
|
56
|
+
*/
|
|
57
|
+
export declare function compareSignatureConfigs(config1: SignatureConfig, config2: SignatureConfig): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Clone a signature config
|
|
60
|
+
*/
|
|
61
|
+
export declare function cloneSignatureConfig(config: SignatureConfig): SignatureConfig;
|
|
62
|
+
/**
|
|
63
|
+
* Merge signature configs (config2 overrides config1)
|
|
64
|
+
*/
|
|
65
|
+
export declare function mergeSignatureConfigs(config1: SignatureConfig, config2: Partial<SignatureConfig>): SignatureConfig;
|
|
66
|
+
/**
|
|
67
|
+
* Validate platform configuration
|
|
68
|
+
*/
|
|
69
|
+
export declare function validatePlatformConfig(platform: WebhookPlatform): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Get all valid platforms
|
|
72
|
+
*/
|
|
73
|
+
export declare function getValidPlatforms(): WebhookPlatform[];
|
|
74
|
+
/**
|
|
75
|
+
* Get platforms by algorithm type
|
|
76
|
+
*/
|
|
77
|
+
export declare function getPlatformsByAlgorithmType(): Record<string, WebhookPlatform[]>;
|
|
78
|
+
export declare function cleanHeaders(headers: Record<string, string | undefined>): Record<string, string>;
|