@hookflo/tern 3.0.1 → 3.0.2
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.
|
@@ -6,7 +6,7 @@ export declare abstract class AlgorithmBasedVerifier extends WebhookVerifier {
|
|
|
6
6
|
constructor(secret: string, config: SignatureConfig, platform: WebhookPlatform, toleranceInSeconds?: number);
|
|
7
7
|
abstract verify(request: Request): Promise<WebhookVerificationResult>;
|
|
8
8
|
protected parseDelimitedHeader(headerValue: string): Record<string, string>;
|
|
9
|
-
protected
|
|
9
|
+
protected extractSignatures(request: Request): string[];
|
|
10
10
|
protected extractTimestamp(request: Request): number | null;
|
|
11
11
|
protected extractTimestampFromSignature(request: Request): number | null;
|
|
12
12
|
protected formatPayload(rawBody: string, request: Request): string;
|
|
@@ -31,31 +31,51 @@ class AlgorithmBasedVerifier extends base_1.WebhookVerifier {
|
|
|
31
31
|
}
|
|
32
32
|
return values;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
extractSignatures(request) {
|
|
35
35
|
const headerValue = request.headers.get(this.config.headerName);
|
|
36
36
|
if (!headerValue)
|
|
37
|
-
return
|
|
37
|
+
return [];
|
|
38
38
|
switch (this.config.headerFormat) {
|
|
39
39
|
case "prefixed":
|
|
40
|
-
return headerValue.trim();
|
|
40
|
+
return [headerValue.trim()].filter(Boolean);
|
|
41
41
|
case "comma-separated": {
|
|
42
42
|
const sigMap = this.parseDelimitedHeader(headerValue);
|
|
43
43
|
const signatureKey = this.config.customConfig?.signatureKey || "v1";
|
|
44
|
-
return
|
|
44
|
+
return [
|
|
45
|
+
sigMap[signatureKey],
|
|
46
|
+
sigMap.signature,
|
|
47
|
+
sigMap.v1,
|
|
48
|
+
].filter((value) => Boolean(value));
|
|
45
49
|
}
|
|
46
50
|
case "raw":
|
|
47
51
|
default:
|
|
48
52
|
if (this.config.customConfig?.signatureFormat?.includes("v1=")) {
|
|
49
|
-
const signatures = headerValue
|
|
53
|
+
const signatures = headerValue
|
|
54
|
+
.trim()
|
|
55
|
+
.split(/\s+/)
|
|
56
|
+
.map((entry) => entry.trim())
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
const normalized = [];
|
|
50
59
|
for (const sig of signatures) {
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
60
|
+
// Standard Webhooks format is usually "v1,<signature>"
|
|
61
|
+
if (sig.startsWith("v1,")) {
|
|
62
|
+
const [, value] = sig.split(",", 2);
|
|
63
|
+
if (value) {
|
|
64
|
+
normalized.push(value.trim());
|
|
65
|
+
}
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Accept "v1=<signature>" variants used by some providers/docs.
|
|
69
|
+
if (sig.startsWith("v1=")) {
|
|
70
|
+
const [, value] = sig.split("=", 2);
|
|
71
|
+
if (value) {
|
|
72
|
+
normalized.push(value.trim());
|
|
73
|
+
}
|
|
54
74
|
}
|
|
55
75
|
}
|
|
56
|
-
return
|
|
76
|
+
return normalized;
|
|
57
77
|
}
|
|
58
|
-
return headerValue;
|
|
78
|
+
return [headerValue.trim()].filter(Boolean);
|
|
59
79
|
}
|
|
60
80
|
}
|
|
61
81
|
extractTimestamp(request) {
|
|
@@ -239,8 +259,8 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
239
259
|
}
|
|
240
260
|
async verify(request) {
|
|
241
261
|
try {
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
262
|
+
const signatures = this.extractSignatures(request);
|
|
263
|
+
if (signatures.length === 0) {
|
|
244
264
|
return {
|
|
245
265
|
isValid: false,
|
|
246
266
|
error: `Missing signature header: ${this.config.headerName}`,
|
|
@@ -270,14 +290,19 @@ class GenericHMACVerifier extends AlgorithmBasedVerifier {
|
|
|
270
290
|
let isValid = false;
|
|
271
291
|
const algorithm = this.config.algorithm.replace("hmac-", "");
|
|
272
292
|
for (const payload of payloadCandidates) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
293
|
+
for (const signature of signatures) {
|
|
294
|
+
if (this.config.customConfig?.encoding === "base64") {
|
|
295
|
+
isValid = this.verifyHMACWithBase64(payload, signature, algorithm);
|
|
296
|
+
}
|
|
297
|
+
else if (this.config.headerFormat === "prefixed") {
|
|
298
|
+
isValid = this.verifyHMACWithPrefix(payload, signature, algorithm);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
isValid = this.verifyHMAC(payload, signature, algorithm);
|
|
302
|
+
}
|
|
303
|
+
if (isValid) {
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
281
306
|
}
|
|
282
307
|
if (isValid) {
|
|
283
308
|
break;
|
|
@@ -412,8 +437,8 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
|
412
437
|
}
|
|
413
438
|
async verify(request) {
|
|
414
439
|
try {
|
|
415
|
-
const
|
|
416
|
-
if (
|
|
440
|
+
const signatures = this.extractSignatures(request);
|
|
441
|
+
if (signatures.length === 0) {
|
|
417
442
|
return {
|
|
418
443
|
isValid: false,
|
|
419
444
|
error: `Missing signature header: ${this.config.headerName}`,
|
|
@@ -466,23 +491,28 @@ class Ed25519Verifier extends AlgorithmBasedVerifier {
|
|
|
466
491
|
// fal.ai signature is hex encoded (not base64)
|
|
467
492
|
// other ED25519 platforms use base64 by default
|
|
468
493
|
const signatureEncoding = this.platform === "falai" ? "hex" : "base64";
|
|
469
|
-
const signatureBytes = new Uint8Array(Buffer.from(signature, signatureEncoding));
|
|
470
494
|
const payloadBytes = new Uint8Array(Buffer.from(payload, "utf-8"));
|
|
471
495
|
// try all keys — succeeds if any key validates
|
|
472
496
|
// this handles key rotation gracefully
|
|
473
497
|
let isValid = false;
|
|
474
|
-
for (const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
498
|
+
for (const signature of signatures) {
|
|
499
|
+
const signatureBytes = new Uint8Array(Buffer.from(signature, signatureEncoding));
|
|
500
|
+
for (const pem of publicKeys) {
|
|
501
|
+
try {
|
|
502
|
+
const keyObject = (0, crypto_1.createPublicKey)(pem);
|
|
503
|
+
const valid = (0, crypto_1.verify)(null, payloadBytes, keyObject, signatureBytes);
|
|
504
|
+
if (valid) {
|
|
505
|
+
isValid = true;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
// this key failed — try next
|
|
511
|
+
continue;
|
|
481
512
|
}
|
|
482
513
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
continue;
|
|
514
|
+
if (isValid) {
|
|
515
|
+
break;
|
|
486
516
|
}
|
|
487
517
|
}
|
|
488
518
|
if (!isValid) {
|
package/package.json
CHANGED