hooklens 0.1.1 → 0.2.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/dist/index.js +158 -47
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command as Command4 } from "commander";
|
|
5
5
|
|
|
6
|
+
// src/errors.ts
|
|
7
|
+
function toError(value) {
|
|
8
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
9
|
+
}
|
|
10
|
+
function errorMessage(value) {
|
|
11
|
+
return value instanceof Error ? value.message : String(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
// src/cli/listen.ts
|
|
7
15
|
import { Command } from "commander";
|
|
8
16
|
|
|
@@ -180,7 +188,10 @@ async function forwardEvent(targetUrl, event, timeoutMs = DEFAULT_FORWARD_TIMEOU
|
|
|
180
188
|
if (isAbortError(error)) {
|
|
181
189
|
throw new Error(`forward timed out after ${timeoutMs}ms`);
|
|
182
190
|
}
|
|
183
|
-
|
|
191
|
+
const cause = error instanceof Error ? error.cause : void 0;
|
|
192
|
+
const code = cause instanceof Error ? cause.code : void 0;
|
|
193
|
+
const message = cause instanceof Error && cause.message ? cause.message : code;
|
|
194
|
+
throw new Error(message ?? errorMessage(error));
|
|
184
195
|
} finally {
|
|
185
196
|
clearTimeout(timeout);
|
|
186
197
|
}
|
|
@@ -213,9 +224,14 @@ function createServer(opts) {
|
|
|
213
224
|
const forwarded = await forwardEvent(opts.forwardTo, event, forwardTimeoutMs);
|
|
214
225
|
res.statusCode = forwarded.status;
|
|
215
226
|
res.end(forwarded.body);
|
|
216
|
-
} catch {
|
|
227
|
+
} catch (error) {
|
|
228
|
+
const err = toError(error);
|
|
229
|
+
try {
|
|
230
|
+
opts.onForwardError?.(event, err);
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
217
233
|
res.statusCode = 502;
|
|
218
|
-
res.end(
|
|
234
|
+
res.end(`bad gateway: ${err.message}`);
|
|
219
235
|
}
|
|
220
236
|
};
|
|
221
237
|
return {
|
|
@@ -235,7 +251,7 @@ function createServer(opts) {
|
|
|
235
251
|
return;
|
|
236
252
|
}
|
|
237
253
|
res.statusCode = 500;
|
|
238
|
-
res.end(
|
|
254
|
+
res.end(errorMessage(err));
|
|
239
255
|
});
|
|
240
256
|
});
|
|
241
257
|
httpServer = server;
|
|
@@ -428,6 +444,9 @@ function createTerminal(stdout = process.stdout, stderr = process.stderr) {
|
|
|
428
444
|
}
|
|
429
445
|
writeLine(stdout, `${summary} ${result.message}`);
|
|
430
446
|
},
|
|
447
|
+
printForwardError(eventId, reason) {
|
|
448
|
+
writeLine(stdout, `${chalk.red("FWD")} ${chalk.bold(eventId)} ${reason}`);
|
|
449
|
+
},
|
|
431
450
|
printEventList(events) {
|
|
432
451
|
if (!events.length) {
|
|
433
452
|
writeLine(stdout, chalk.dim("No stored events."));
|
|
@@ -455,7 +474,7 @@ function createTerminal(stdout = process.stdout, stderr = process.stderr) {
|
|
|
455
474
|
};
|
|
456
475
|
}
|
|
457
476
|
|
|
458
|
-
// src/verify/
|
|
477
|
+
// src/verify/github.ts
|
|
459
478
|
import crypto2 from "crypto";
|
|
460
479
|
|
|
461
480
|
// src/verify/headers.ts
|
|
@@ -466,10 +485,86 @@ function getHeaderCaseInsensitive(headers, name) {
|
|
|
466
485
|
}
|
|
467
486
|
return void 0;
|
|
468
487
|
}
|
|
488
|
+
function tryCanonicalForm(payload) {
|
|
489
|
+
try {
|
|
490
|
+
const canonical = JSON.stringify(JSON.parse(payload));
|
|
491
|
+
return canonical === payload ? null : canonical;
|
|
492
|
+
} catch {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/verify/github.ts
|
|
498
|
+
var PROVIDER = "github";
|
|
499
|
+
var PREFIX = "sha256=";
|
|
500
|
+
var SHA256_HEX = /^[0-9a-fA-F]{64}$/;
|
|
501
|
+
function computeHmac(secret, payload) {
|
|
502
|
+
return crypto2.createHmac("sha256", secret).update(payload).digest("hex");
|
|
503
|
+
}
|
|
504
|
+
function constantTimeMatch(expected, actual) {
|
|
505
|
+
if (expected.length !== actual.length) return false;
|
|
506
|
+
const expectedBuf = Buffer.from(expected, "utf8");
|
|
507
|
+
const actualBuf = Buffer.from(actual, "utf8");
|
|
508
|
+
return crypto2.timingSafeEqual(expectedBuf, actualBuf);
|
|
509
|
+
}
|
|
510
|
+
function success(message) {
|
|
511
|
+
return { valid: true, provider: PROVIDER, code: "valid", message };
|
|
512
|
+
}
|
|
513
|
+
function failure(code, message) {
|
|
514
|
+
return { valid: false, provider: PROVIDER, code, message };
|
|
515
|
+
}
|
|
516
|
+
function verifyGitHubSignature(opts) {
|
|
517
|
+
if (!opts.header) {
|
|
518
|
+
return failure(
|
|
519
|
+
"missing_header",
|
|
520
|
+
"x-hub-signature-256 header not found. Is this actually from GitHub?"
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
if (!opts.header.startsWith(PREFIX)) {
|
|
524
|
+
return failure("malformed_header", "x-hub-signature-256 header must start with sha256=");
|
|
525
|
+
}
|
|
526
|
+
const signature = opts.header.slice(PREFIX.length);
|
|
527
|
+
if (signature.length === 0) {
|
|
528
|
+
return failure("malformed_header", "x-hub-signature-256 header has no signature after sha256=");
|
|
529
|
+
}
|
|
530
|
+
if (!SHA256_HEX.test(signature)) {
|
|
531
|
+
return failure("malformed_header", "x-hub-signature-256 header has invalid sha256 hex digest");
|
|
532
|
+
}
|
|
533
|
+
const normalizedSignature = signature.toLowerCase();
|
|
534
|
+
const expected = computeHmac(opts.secret, opts.payload);
|
|
535
|
+
if (constantTimeMatch(expected, normalizedSignature)) {
|
|
536
|
+
return success("Signature verified.");
|
|
537
|
+
}
|
|
538
|
+
const canonical = tryCanonicalForm(opts.payload);
|
|
539
|
+
if (canonical !== null) {
|
|
540
|
+
const expectedCanonical = computeHmac(opts.secret, canonical);
|
|
541
|
+
if (constantTimeMatch(expectedCanonical, normalizedSignature)) {
|
|
542
|
+
return failure(
|
|
543
|
+
"body_mutated",
|
|
544
|
+
"Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework."
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return failure(
|
|
549
|
+
"signature_mismatch",
|
|
550
|
+
"Signature mismatch. Check your webhook secret matches the GitHub settings."
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
function createGitHubVerifier(opts) {
|
|
554
|
+
return {
|
|
555
|
+
provider: PROVIDER,
|
|
556
|
+
verify: (event) => verifyGitHubSignature({
|
|
557
|
+
payload: event.body,
|
|
558
|
+
header: getHeaderCaseInsensitive(event.headers, "x-hub-signature-256"),
|
|
559
|
+
secret: opts.secret
|
|
560
|
+
})
|
|
561
|
+
};
|
|
562
|
+
}
|
|
469
563
|
|
|
470
564
|
// src/verify/stripe.ts
|
|
565
|
+
import crypto3 from "crypto";
|
|
471
566
|
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
472
|
-
var
|
|
567
|
+
var PROVIDER2 = "stripe";
|
|
473
568
|
function parseHeader(header) {
|
|
474
569
|
let timestamp = null;
|
|
475
570
|
const signatures = [];
|
|
@@ -489,42 +584,34 @@ function parseHeader(header) {
|
|
|
489
584
|
if (timestamp === null || signatures.length === 0) return null;
|
|
490
585
|
return { timestamp, signatures };
|
|
491
586
|
}
|
|
492
|
-
function
|
|
493
|
-
return
|
|
587
|
+
function computeHmac2(secret, signedPayload) {
|
|
588
|
+
return crypto3.createHmac("sha256", secret).update(signedPayload).digest("hex");
|
|
494
589
|
}
|
|
495
|
-
function
|
|
590
|
+
function constantTimeMatch2(expected, candidates) {
|
|
496
591
|
const expectedBuf = Buffer.from(expected, "utf8");
|
|
497
592
|
for (const candidate of candidates) {
|
|
498
593
|
if (candidate.length !== expected.length) continue;
|
|
499
594
|
const candidateBuf = Buffer.from(candidate, "utf8");
|
|
500
|
-
if (
|
|
595
|
+
if (crypto3.timingSafeEqual(expectedBuf, candidateBuf)) return true;
|
|
501
596
|
}
|
|
502
597
|
return false;
|
|
503
598
|
}
|
|
504
|
-
function
|
|
505
|
-
|
|
506
|
-
const canonical = JSON.stringify(JSON.parse(payload));
|
|
507
|
-
return canonical === payload ? null : canonical;
|
|
508
|
-
} catch {
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
function success(message) {
|
|
513
|
-
return { valid: true, provider: PROVIDER, code: "valid", message };
|
|
599
|
+
function success2(message) {
|
|
600
|
+
return { valid: true, provider: PROVIDER2, code: "valid", message };
|
|
514
601
|
}
|
|
515
|
-
function
|
|
516
|
-
return { valid: false, provider:
|
|
602
|
+
function failure2(code, message) {
|
|
603
|
+
return { valid: false, provider: PROVIDER2, code, message };
|
|
517
604
|
}
|
|
518
605
|
function verifyStripeSignature(opts) {
|
|
519
606
|
if (!opts.header) {
|
|
520
|
-
return
|
|
607
|
+
return failure2(
|
|
521
608
|
"missing_header",
|
|
522
609
|
"stripe-signature header not found. Is this actually from Stripe?"
|
|
523
610
|
);
|
|
524
611
|
}
|
|
525
612
|
const parsed = parseHeader(opts.header);
|
|
526
613
|
if (!parsed) {
|
|
527
|
-
return
|
|
614
|
+
return failure2(
|
|
528
615
|
"malformed_header",
|
|
529
616
|
"stripe-signature header is malformed. Expected format: t=timestamp,v1=signature"
|
|
530
617
|
);
|
|
@@ -534,34 +621,34 @@ function verifyStripeSignature(opts) {
|
|
|
534
621
|
const ageSeconds = Math.floor(nowMs / 1e3) - parsed.timestamp;
|
|
535
622
|
if (ageSeconds > tolerance) {
|
|
536
623
|
const minutes = Math.floor(ageSeconds / 60);
|
|
537
|
-
return
|
|
624
|
+
return failure2(
|
|
538
625
|
"expired_timestamp",
|
|
539
626
|
`Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`
|
|
540
627
|
);
|
|
541
628
|
}
|
|
542
629
|
const signedPayload = `${parsed.timestamp}.${opts.payload}`;
|
|
543
|
-
const expected =
|
|
544
|
-
if (
|
|
545
|
-
return
|
|
630
|
+
const expected = computeHmac2(opts.secret, signedPayload);
|
|
631
|
+
if (constantTimeMatch2(expected, parsed.signatures)) {
|
|
632
|
+
return success2("Signature verified.");
|
|
546
633
|
}
|
|
547
634
|
const canonical = tryCanonicalForm(opts.payload);
|
|
548
635
|
if (canonical !== null) {
|
|
549
|
-
const expectedCanonical =
|
|
550
|
-
if (
|
|
551
|
-
return
|
|
636
|
+
const expectedCanonical = computeHmac2(opts.secret, `${parsed.timestamp}.${canonical}`);
|
|
637
|
+
if (constantTimeMatch2(expectedCanonical, parsed.signatures)) {
|
|
638
|
+
return failure2(
|
|
552
639
|
"body_mutated",
|
|
553
640
|
"Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework."
|
|
554
641
|
);
|
|
555
642
|
}
|
|
556
643
|
}
|
|
557
|
-
return
|
|
644
|
+
return failure2(
|
|
558
645
|
"signature_mismatch",
|
|
559
646
|
"Signature mismatch. Check your webhook secret matches the Stripe dashboard."
|
|
560
647
|
);
|
|
561
648
|
}
|
|
562
649
|
function createStripeVerifier(opts) {
|
|
563
650
|
return {
|
|
564
|
-
provider:
|
|
651
|
+
provider: PROVIDER2,
|
|
565
652
|
verify: (event) => verifyStripeSignature({
|
|
566
653
|
payload: event.body,
|
|
567
654
|
header: getHeaderCaseInsensitive(event.headers, "stripe-signature"),
|
|
@@ -589,8 +676,14 @@ function buildVerifier(flags) {
|
|
|
589
676
|
}
|
|
590
677
|
return createStripeVerifier({ secret: flags.secret });
|
|
591
678
|
}
|
|
679
|
+
case "github": {
|
|
680
|
+
if (!flags.secret) {
|
|
681
|
+
throw new Error("--secret is required when --verify github is set");
|
|
682
|
+
}
|
|
683
|
+
return createGitHubVerifier({ secret: flags.secret });
|
|
684
|
+
}
|
|
592
685
|
default:
|
|
593
|
-
throw new Error(`Unknown --verify provider "${flags.verify}". Supported: stripe`);
|
|
686
|
+
throw new Error(`Unknown --verify provider "${flags.verify}". Supported: stripe, github`);
|
|
594
687
|
}
|
|
595
688
|
}
|
|
596
689
|
async function stopServer(server) {
|
|
@@ -601,8 +694,7 @@ function printEventCapturedBestEffort(terminal, event, result) {
|
|
|
601
694
|
try {
|
|
602
695
|
terminal.printEventCaptured(event, result);
|
|
603
696
|
} catch (error) {
|
|
604
|
-
|
|
605
|
-
console.error(`Failed to print captured event: ${message}`);
|
|
697
|
+
console.error(`Failed to print captured event: ${errorMessage(error)}`);
|
|
606
698
|
}
|
|
607
699
|
}
|
|
608
700
|
async function runListen(flags, deps = {}) {
|
|
@@ -656,7 +748,8 @@ async function runListen(flags, deps = {}) {
|
|
|
656
748
|
storage,
|
|
657
749
|
verifier,
|
|
658
750
|
forwardTo: flags.forwardTo,
|
|
659
|
-
onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result)
|
|
751
|
+
onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),
|
|
752
|
+
onForwardError: (event, error) => terminal.printForwardError(event.id, error.message)
|
|
660
753
|
});
|
|
661
754
|
signals.on("SIGINT", onSignal);
|
|
662
755
|
signals.on("SIGTERM", onSignal);
|
|
@@ -680,12 +773,20 @@ async function runListen(flags, deps = {}) {
|
|
|
680
773
|
throw error;
|
|
681
774
|
}
|
|
682
775
|
}
|
|
683
|
-
var listenCommand = new Command("listen").description("Start receiving webhooks").option("-p, --port <port>", "Port to listen on", "4400").option("--verify <provider>", "Verify signatures (stripe)").option("--secret <secret>", "Webhook signing secret").option("--forward-to <url>", "Forward received webhooks to this URL").
|
|
776
|
+
var listenCommand = new Command("listen").description("Start receiving webhooks").option("-p, --port <port>", "Port to listen on", "4400").option("--verify <provider>", "Verify signatures (stripe, github)").option("--secret <secret>", "Webhook signing secret").option("--forward-to <url>", "Forward received webhooks to this URL").addHelpText(
|
|
777
|
+
"after",
|
|
778
|
+
`
|
|
779
|
+
Examples:
|
|
780
|
+
hooklens listen
|
|
781
|
+
hooklens listen -p 8080 --forward-to http://localhost:3000/webhook
|
|
782
|
+
hooklens listen --verify stripe --secret whsec_xxx
|
|
783
|
+
hooklens listen --verify github --secret ghsecret_xxx`
|
|
784
|
+
).action(async (options) => {
|
|
684
785
|
const terminal = createTerminal();
|
|
685
786
|
try {
|
|
686
787
|
await runListen(options, { terminal });
|
|
687
788
|
} catch (error) {
|
|
688
|
-
terminal.printError(
|
|
789
|
+
terminal.printError(errorMessage(error));
|
|
689
790
|
process.exitCode = 1;
|
|
690
791
|
}
|
|
691
792
|
});
|
|
@@ -711,12 +812,18 @@ async function runList(flags, deps = {}) {
|
|
|
711
812
|
storage.close();
|
|
712
813
|
}
|
|
713
814
|
}
|
|
714
|
-
var listCommand = new Command2("list").description("Show received webhook events").option("-n, --limit <count>", "Number of events to show", "20").
|
|
815
|
+
var listCommand = new Command2("list").description("Show received webhook events").option("-n, --limit <count>", "Number of events to show", "20").addHelpText(
|
|
816
|
+
"after",
|
|
817
|
+
`
|
|
818
|
+
Examples:
|
|
819
|
+
hooklens list
|
|
820
|
+
hooklens list -n 5`
|
|
821
|
+
).action(async (options) => {
|
|
715
822
|
const terminal = createTerminal();
|
|
716
823
|
try {
|
|
717
824
|
await runList(options, { terminal });
|
|
718
825
|
} catch (error) {
|
|
719
|
-
terminal.printError(
|
|
826
|
+
terminal.printError(errorMessage(error));
|
|
720
827
|
process.exitCode = 1;
|
|
721
828
|
}
|
|
722
829
|
});
|
|
@@ -749,19 +856,24 @@ async function runReplay(eventId, flags, deps = {}) {
|
|
|
749
856
|
body
|
|
750
857
|
});
|
|
751
858
|
} catch (error) {
|
|
752
|
-
|
|
753
|
-
throw new Error(`Failed to replay "${eventId}" to ${targetUrl}: ${message}`);
|
|
859
|
+
throw new Error(`Failed to replay "${eventId}" to ${targetUrl}: ${errorMessage(error)}`);
|
|
754
860
|
}
|
|
755
861
|
} finally {
|
|
756
862
|
storage.close();
|
|
757
863
|
}
|
|
758
864
|
}
|
|
759
|
-
var replayCommand = new Command3("replay").description("Replay a stored webhook event").argument("<event-id>", "ID of the event to replay").option("--to <url>", "Target URL to send the event to", DEFAULT_REPLAY_TARGET_URL).
|
|
865
|
+
var replayCommand = new Command3("replay").description("Replay a stored webhook event").argument("<event-id>", "ID of the event to replay").option("--to <url>", "Target URL to send the event to", DEFAULT_REPLAY_TARGET_URL).addHelpText(
|
|
866
|
+
"after",
|
|
867
|
+
`
|
|
868
|
+
Examples:
|
|
869
|
+
hooklens replay evt_abc123
|
|
870
|
+
hooklens replay evt_abc123 --to http://localhost:8080/hook`
|
|
871
|
+
).action(async (eventId, options) => {
|
|
760
872
|
const terminal = createTerminal();
|
|
761
873
|
try {
|
|
762
874
|
await runReplay(eventId, options, { terminal });
|
|
763
875
|
} catch (error) {
|
|
764
|
-
terminal.printError(
|
|
876
|
+
terminal.printError(errorMessage(error));
|
|
765
877
|
process.exitCode = 1;
|
|
766
878
|
}
|
|
767
879
|
});
|
|
@@ -775,8 +887,7 @@ program.addCommand(replayCommand);
|
|
|
775
887
|
try {
|
|
776
888
|
await program.parseAsync(process.argv);
|
|
777
889
|
} catch (error) {
|
|
778
|
-
|
|
779
|
-
console.error(message);
|
|
890
|
+
console.error(errorMessage(error));
|
|
780
891
|
process.exitCode = 1;
|
|
781
892
|
}
|
|
782
893
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/index.ts","../src/cli/listen.ts","../src/server/index.ts","../src/storage/index.ts","../src/types.ts","../src/ui/terminal.ts","../src/verify/stripe.ts","../src/verify/headers.ts","../src/cli/list.ts","../src/cli/replay.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { listenCommand } from './listen.js'\nimport { listCommand } from './list.js'\nimport { replayCommand } from './replay.js'\n\nconst program = new Command()\n\nprogram\n .name('hooklens')\n .description('Inspect, verify, and replay webhooks from your terminal')\n .version('0.1.0')\n\nprogram.addCommand(listenCommand)\nprogram.addCommand(listCommand)\nprogram.addCommand(replayCommand)\n\ntry {\n await program.parseAsync(process.argv)\n} catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(message)\n process.exitCode = 1\n}\n","import { Command } from 'commander'\nimport { createServer, type Server } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport type { VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { createStripeVerifier } from '../verify/stripe.js'\n\nexport interface ListenFlags {\n port?: string | number\n verify?: string\n secret?: string\n forwardTo?: string\n}\n\nexport interface SignalBus {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n off(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n}\n\nexport interface ListenDeps {\n signals?: SignalBus\n terminal?: TerminalUI\n}\n\nfunction parsePort(port: string | number | undefined): number {\n const raw = port\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {\n throw new Error(`Invalid port \"${raw}\". Expected an integer between 0 and 65535.`)\n }\n\n return parsed\n}\n\n/** Maps --verify flags to a Verifier. See CONTRIBUTING.md → Adding a provider. */\nexport function buildVerifier(flags: ListenFlags): Verifier | undefined {\n if (!flags.verify) return undefined\n\n switch (flags.verify) {\n case 'stripe': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify stripe is set')\n }\n return createStripeVerifier({ secret: flags.secret })\n }\n default:\n throw new Error(`Unknown --verify provider \"${flags.verify}\". Supported: stripe`)\n }\n}\n\nasync function stopServer(server: Server | null): Promise<void> {\n if (!server) return\n await server.stop()\n}\n\nfunction printEventCapturedBestEffort(\n terminal: TerminalUI,\n event: WebhookEvent,\n result: VerificationResult | null,\n): void {\n try {\n terminal.printEventCaptured(event, result)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error(`Failed to print captured event: ${message}`)\n }\n}\n\nexport async function runListen(flags: ListenFlags, deps: ListenDeps = {}): Promise<void> {\n const port = parsePort(flags.port)\n const verifier = buildVerifier(flags)\n const dbPath = defaultDbPath()\n const signals = deps.signals ?? process\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(dbPath)\n\n let server: Server | null = null\n let cleanedUp = false\n let listenersAttached = false\n\n const cleanup = async (printStopped: boolean): Promise<void> => {\n if (cleanedUp) return\n cleanedUp = true\n if (listenersAttached) {\n signals.off('SIGINT', onSignal)\n signals.off('SIGTERM', onSignal)\n listenersAttached = false\n }\n\n let stopError: unknown = null\n\n try {\n await stopServer(server)\n } catch (error) {\n stopError = error\n } finally {\n storage.close()\n }\n\n if (stopError) {\n throw stopError\n }\n\n if (printStopped) {\n terminal.printListenStopped()\n }\n }\n\n let settle: (() => void) | null = null\n let fail: ((error: unknown) => void) | null = null\n const shutdown = new Promise<void>((resolve, reject) => {\n settle = resolve\n fail = reject\n })\n\n const onSignal = () => {\n void cleanup(true).then(\n () => settle?.(),\n (error) => fail?.(error),\n )\n }\n\n try {\n server = createServer({\n port,\n storage,\n verifier,\n forwardTo: flags.forwardTo,\n onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),\n })\n\n signals.on('SIGINT', onSignal)\n signals.on('SIGTERM', onSignal)\n listenersAttached = true\n\n await server.start()\n\n if (cleanedUp) {\n return await shutdown\n }\n\n terminal.printListenStarted({\n port: server.port,\n dbPath,\n verifier: verifier?.provider,\n forwardTo: flags.forwardTo,\n })\n\n return await shutdown\n } catch (error) {\n if (cleanedUp) {\n return await shutdown\n }\n\n await cleanup(false)\n throw error\n }\n}\n\nexport const listenCommand = new Command('listen')\n .description('Start receiving webhooks')\n .option('-p, --port <port>', 'Port to listen on', '4400')\n .option('--verify <provider>', 'Verify signatures (stripe)')\n .option('--secret <secret>', 'Webhook signing secret')\n .option('--forward-to <url>', 'Forward received webhooks to this URL')\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runListen(options, { terminal })\n } catch (error) {\n terminal.printError(error instanceof Error ? error.message : String(error))\n\n process.exitCode = 1\n }\n })\n","import http from 'node:http'\nimport crypto from 'node:crypto'\nimport type { ReplayResult, VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport type { createStorage } from '../storage/index.js'\n\ntype Storage = ReturnType<typeof createStorage>\n\nexport interface ServerOptions {\n port: number\n storage: Storage\n verifier?: Verifier\n forwardTo?: string\n forwardTimeoutMs?: number\n maxBodyBytes?: number\n onEvent?: (event: WebhookEvent, result: VerificationResult | null) => void\n}\n\nexport interface Server {\n readonly port: number\n start(): Promise<void>\n stop(): Promise<void>\n}\n\n// Headers we strip before forwarding. This is the RFC 7230 section 6.1\n// hop-by-hop list plus host (fetch sets this from the destination URL) and\n// content-length (fetch recomputes this from the body).\nconst FORWARD_STRIP = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'host',\n 'content-length',\n])\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst DEFAULT_FORWARD_TIMEOUT_MS = 5000\n\nfunction forwardedStripSet(headers: Record<string, string>): Set<string> {\n const strip = new Set(FORWARD_STRIP)\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== 'connection') continue\n for (const token of value.split(/[,\\s]+/)) {\n const name = token.trim().toLowerCase()\n if (name) strip.add(name)\n }\n }\n return strip\n}\n\nfunction generateEventId(): string {\n return `evt_${crypto.randomBytes(12).toString('base64url')}`\n}\n\nclass PayloadTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`payload too large: max ${maxBytes} bytes`)\n this.name = 'PayloadTooLargeError'\n }\n}\n\nfunction isPayloadTooLargeError(error: unknown): error is PayloadTooLargeError {\n return error instanceof PayloadTooLargeError\n}\n\nfunction requestSockets(req: http.IncomingMessage): NodeJS.EventEmitter[] {\n const sockets = new Set<NodeJS.EventEmitter>()\n sockets.add(req.socket)\n const proxiedSocket = (req.socket as typeof req.socket & { proxy?: NodeJS.EventEmitter | null })\n .proxy\n if (proxiedSocket) sockets.add(proxiedSocket)\n return [...sockets]\n}\n\nexport function readBody(req: http.IncomingMessage, maxBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalBytes = 0\n let settled = false\n const sockets = requestSockets(req)\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onError)\n for (const socket of sockets) {\n socket.off('close', onSocketClose)\n socket.off('error', onSocketError)\n }\n }\n\n const rejectOnce = (error: Error) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n }\n\n const resolveOnce = (body: string) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(body)\n }\n\n const onData = (chunk: Buffer) => {\n totalBytes += chunk.length\n if (totalBytes > maxBytes) {\n req.resume()\n rejectOnce(new PayloadTooLargeError(maxBytes))\n return\n }\n chunks.push(chunk)\n }\n\n const onEnd = () => resolveOnce(Buffer.concat(chunks, totalBytes).toString('utf8'))\n const onError = (error: Error) => rejectOnce(error)\n const onSocketClose = () => rejectOnce(new Error('socket closed during request body'))\n const onSocketError = (error: Error) => rejectOnce(error)\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onError)\n for (const socket of sockets) {\n socket.on('close', onSocketClose)\n socket.on('error', onSocketError)\n }\n })\n}\n\nfunction headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\nexport function headersForForwarding(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {}\n const strip = forwardedStripSet(headers)\n for (const [key, value] of Object.entries(headers)) {\n if (!strip.has(key.toLowerCase())) out[key] = value\n }\n return out\n}\n\ninterface ParsedEventPath {\n pathname: string\n search: string\n}\n\nfunction forwardPathname(targetPathname: string, incomingPathname: string): string {\n if (targetPathname === '/' || targetPathname === '') return incomingPathname || '/'\n if (incomingPathname === '/' || incomingPathname === '') return targetPathname\n const base = targetPathname.endsWith('/') ? targetPathname.slice(0, -1) : targetPathname\n const incoming = incomingPathname.startsWith('/') ? incomingPathname : `/${incomingPathname}`\n return `${base}${incoming}`\n}\n\nexport function parseEventPath(path: string): ParsedEventPath {\n if (/^[A-Za-z][A-Za-z\\d+.-]*:/.test(path)) {\n const parsed = new URL(path)\n return { pathname: parsed.pathname, search: parsed.search }\n }\n\n const queryIndex = path.indexOf('?')\n if (queryIndex === -1) {\n return { pathname: path, search: '' }\n }\n\n return {\n pathname: path.slice(0, queryIndex),\n search: path.slice(queryIndex),\n }\n}\n\nfunction mergeForwardSearch(targetSearch: string, incomingSearch: string): string {\n const merged = new URLSearchParams(incomingSearch)\n const trusted = new URLSearchParams(targetSearch)\n\n for (const key of new Set(trusted.keys())) {\n merged.delete(key)\n }\n for (const [key, value] of trusted) {\n merged.append(key, value)\n }\n\n const search = merged.toString()\n return search.length > 0 ? `?${search}` : ''\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nexport async function forwardEvent(\n targetUrl: string,\n event: WebhookEvent,\n timeoutMs = DEFAULT_FORWARD_TIMEOUT_MS,\n): Promise<ReplayResult> {\n const target = new URL(targetUrl)\n const destination = new URL(target.href)\n const parsedEventPath = parseEventPath(event.path)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n\n destination.pathname = forwardPathname(destination.pathname, parsedEventPath.pathname)\n destination.search = mergeForwardSearch(destination.search, parsedEventPath.search)\n\n try {\n const hasBody = event.method !== 'GET' && event.method !== 'HEAD'\n const response = await fetch(destination, {\n method: event.method,\n headers: headersForForwarding(event.headers),\n body: hasBody ? event.body : undefined,\n signal: controller.signal,\n })\n\n return {\n status: response.status,\n body: await response.text(),\n }\n } catch (error) {\n if (isAbortError(error)) {\n throw new Error(`forward timed out after ${timeoutMs}ms`)\n }\n throw error\n } finally {\n clearTimeout(timeout)\n }\n}\n\nexport function createServer(opts: ServerOptions): Server {\n let boundPort = opts.port\n let httpServer: http.Server | null = null\n let isStarting = false\n const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const forwardTimeoutMs = opts.forwardTimeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS\n\n const handleRequest = async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> => {\n const body = await readBody(req, maxBodyBytes)\n\n const event: WebhookEvent = {\n id: generateEventId(),\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: headersToRecord(req.headers),\n body,\n }\n\n opts.storage.save(event)\n const verification = opts.verifier?.verify({ headers: event.headers, body: event.body }) ?? null\n opts.onEvent?.(event, verification)\n\n if (!opts.forwardTo) {\n res.statusCode = 200\n res.end('ok')\n return\n }\n\n try {\n const forwarded = await forwardEvent(opts.forwardTo, event, forwardTimeoutMs)\n res.statusCode = forwarded.status\n res.end(forwarded.body)\n } catch {\n res.statusCode = 502\n res.end('bad gateway')\n }\n }\n\n return {\n get port() {\n return boundPort\n },\n\n async start() {\n if (httpServer || isStarting) {\n throw new Error('server already started')\n }\n\n isStarting = true\n const server = http.createServer((req, res) => {\n handleRequest(req, res).catch((err: unknown) => {\n if (isPayloadTooLargeError(err)) {\n res.statusCode = 413\n res.end(err.message)\n return\n }\n res.statusCode = 500\n res.end(err instanceof Error ? err.message : String(err))\n })\n })\n httpServer = server\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => {\n server.off('error', onError)\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n reject(err)\n }\n\n server.once('error', onError)\n server.listen(opts.port, '127.0.0.1', () => {\n server.off('error', onError)\n const addr = server.address()\n if (addr && typeof addr !== 'string') {\n boundPort = addr.port\n }\n isStarting = false\n resolve()\n })\n })\n } catch (err) {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n throw err\n }\n },\n\n async stop() {\n if (!httpServer) return\n const server = httpServer\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n if (err) {\n reject(err)\n return\n }\n resolve()\n })\n })\n },\n }\n}\n","import os from 'node:os'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport type * as sqlite from 'node:sqlite'\nimport { eventRowSchema, webhookEventSchema, type EventRow, type WebhookEvent } from '../types.js'\n\nconst require = createRequire(import.meta.url)\n\n// tsup/esbuild currently rewrites a static `node:sqlite` import to `sqlite`,\n// which breaks the built CLI. Resolve it at runtime so the core module specifier\n// survives the bundle unchanged.\nconst { DatabaseSync } = require('node:' + 'sqlite') as typeof sqlite\n\nexport function defaultDbPath(): string {\n return path.join(os.homedir(), '.hooklens', 'events.db')\n}\n\nfunction rowToEvent(row: EventRow): WebhookEvent {\n return webhookEventSchema.parse({\n id: row.id,\n timestamp: row.timestamp,\n method: row.method,\n path: row.path,\n headers: JSON.parse(row.headers),\n body: row.body,\n })\n}\n\nexport function createStorage(dbPath: string) {\n fs.mkdirSync(path.dirname(dbPath), { recursive: true })\n const db = new DatabaseSync(dbPath)\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n method TEXT NOT NULL,\n path TEXT NOT NULL,\n headers TEXT NOT NULL,\n body TEXT NOT NULL\n )\n `)\n\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO events (id, timestamp, method, path, headers, body)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n\n const getStmt = db.prepare(`SELECT * FROM events WHERE id = ?`)\n const listAllStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC`)\n const listLimitedStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`)\n const clearStmt = db.prepare(`DELETE FROM events`)\n\n return {\n save(event: WebhookEvent): void {\n insertStmt.run(\n event.id,\n event.timestamp,\n event.method,\n event.path,\n JSON.stringify(event.headers),\n event.body,\n )\n },\n\n load(id: string): WebhookEvent | null {\n const raw = getStmt.get(id)\n if (!raw) return null\n const row = eventRowSchema.parse(raw)\n return rowToEvent(row)\n },\n\n list(limit?: number): WebhookEvent[] {\n if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {\n throw new Error(`Invalid limit: must be a positive integer, got ${limit}`)\n }\n const raw = limit === undefined ? listAllStmt.all() : listLimitedStmt.all(limit)\n const rows = raw.map((r) => eventRowSchema.parse(r))\n return rows.map(rowToEvent)\n },\n\n clear(): void {\n clearStmt.run()\n },\n\n close(): void {\n db.close()\n },\n }\n}\n","import { z } from 'zod'\n\n// A webhook event as it lives in memory and is exposed to the rest of the app.\n// Headers are a parsed object here -- on disk they're stored as a JSON string.\nexport const webhookEventSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.record(z.string(), z.string()),\n body: z.string(),\n})\n\nexport type WebhookEvent = z.infer<typeof webhookEventSchema>\n\n// The shape of a row read directly from the SQLite events table.\n// headers is a JSON string at this layer; rowToEvent parses it.\nexport const eventRowSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.string(),\n body: z.string(),\n})\n\nexport type EventRow = z.infer<typeof eventRowSchema>\n\nexport const verificationResultSchema = z.object({\n valid: z.boolean(),\n provider: z.string(),\n message: z.string(),\n code: z.enum([\n 'valid',\n 'missing_header',\n 'malformed_header',\n 'expired_timestamp',\n 'signature_mismatch',\n 'body_mutated',\n ]),\n})\n\nexport type VerificationResult = z.infer<typeof verificationResultSchema>\n\nexport const replayResultSchema = z.object({\n status: z.number().int(),\n body: z.string(),\n})\n\nexport type ReplayResult = z.infer<typeof replayResultSchema>\n\n/** Provider signature verifier. See CONTRIBUTING.md → Adding a provider. */\nexport interface Verifier {\n readonly provider: string\n verify(event: { headers: Record<string, string>; body: string }): VerificationResult\n}\n","import chalk from 'chalk'\nimport type { ReplayResult, VerificationResult, WebhookEvent } from '../types.js'\n\nexport interface ListenStartedInfo {\n port: number\n dbPath: string\n verifier?: string\n forwardTo?: string\n}\n\nexport interface TerminalUI {\n printListenStarted(info: ListenStartedInfo): void\n printEventCaptured(event: WebhookEvent, result: VerificationResult | null): void\n printEventList(events: WebhookEvent[]): void\n printReplayResult(result: ReplayResult): void\n printListenStopped(): void\n printError(message: string): void\n}\n\nfunction writeLine(stream: NodeJS.WriteStream, line: string): void {\n stream.write(`${line}\\n`)\n}\n\nfunction verificationLabel(result: VerificationResult | null): string {\n if (!result) return chalk.cyan('RECV')\n return result.valid ? chalk.green('PASS') : chalk.red('FAIL')\n}\n\nexport function createTerminal(\n stdout: NodeJS.WriteStream = process.stdout,\n stderr: NodeJS.WriteStream = process.stderr,\n): TerminalUI {\n return {\n printListenStarted(info) {\n writeLine(\n stdout,\n `${chalk.bold('Listening on')} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`,\n )\n\n writeLine(stdout, `Verifier: ${info.verifier ?? 'none'}`)\n writeLine(stdout, `Forwarding to: ${info.forwardTo ?? 'disabled'}`)\n writeLine(stdout, `Storage: ${info.dbPath}`)\n },\n\n printEventCaptured(event, result) {\n const label = verificationLabel(result)\n const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`\n\n if (!result) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.message}`)\n },\n\n printEventList(events) {\n if (!events.length) {\n writeLine(stdout, chalk.dim('No stored events.'))\n return\n }\n\n for (const event of events) {\n const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`\n writeLine(stdout, row)\n }\n },\n\n printReplayResult(result) {\n const summary = `${chalk.bold('Replay response:')} ${chalk.cyan(String(result.status))}`\n\n if (!result.body) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.body}`)\n },\n\n printListenStopped() {\n writeLine(stdout, chalk.dim('Stopped listening.'))\n },\n\n printError(message) {\n writeLine(stderr, chalk.red(message))\n },\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive } from './headers.js'\n\nexport interface VerifyStripeOptions {\n payload: string\n header: string | null | undefined\n secret: string\n tolerance?: number\n now?: () => number\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\nconst PROVIDER = 'stripe'\n\ninterface ParsedHeader {\n timestamp: number\n signatures: string[]\n}\n\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp: number | null = null\n const signatures: string[] = []\n\n for (const part of header.split(',')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx === -1) return null\n\n const key = part.slice(0, eqIdx)\n const value = part.slice(eqIdx + 1)\n\n if (key === 't') {\n if (!/^\\d+$/.test(value)) return null\n timestamp = Number(value)\n } else if (key === 'v1') {\n if (value.length === 0) return null\n signatures.push(value)\n }\n }\n\n if (timestamp === null || signatures.length === 0) return null\n return { timestamp, signatures }\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return crypto.createHmac('sha256', secret).update(signedPayload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, candidates: string[]): boolean {\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const candidate of candidates) {\n if (candidate.length !== expected.length) continue\n const candidateBuf = Buffer.from(candidate, 'utf8')\n if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) return true\n }\n return false\n}\n\nfunction tryCanonicalForm(payload: string): string | null {\n try {\n const canonical = JSON.stringify(JSON.parse(payload))\n return canonical === payload ? null : canonical\n } catch {\n return null\n }\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyStripeSignature(opts: VerifyStripeOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'stripe-signature header not found. Is this actually from Stripe?',\n )\n }\n\n const parsed = parseHeader(opts.header)\n if (!parsed) {\n return failure(\n 'malformed_header',\n 'stripe-signature header is malformed. Expected format: t=timestamp,v1=signature',\n )\n }\n\n const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n const nowMs = (opts.now ?? Date.now)()\n const ageSeconds = Math.floor(nowMs / 1000) - parsed.timestamp\n\n if (ageSeconds > tolerance) {\n const minutes = Math.floor(ageSeconds / 60)\n return failure(\n 'expired_timestamp',\n `Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`,\n )\n }\n\n const signedPayload = `${parsed.timestamp}.${opts.payload}`\n const expected = computeHmac(opts.secret, signedPayload)\n\n if (constantTimeMatch(expected, parsed.signatures)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, `${parsed.timestamp}.${canonical}`)\n if (constantTimeMatch(expectedCanonical, parsed.signatures)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the Stripe dashboard.',\n )\n}\n\nexport interface StripeVerifierOptions {\n secret: string\n tolerance?: number\n}\n\nexport function createStripeVerifier(opts: StripeVerifierOptions): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyStripeSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'stripe-signature'),\n secret: opts.secret,\n tolerance: opts.tolerance,\n }),\n }\n}\n","export function getHeaderCaseInsensitive(\n headers: Record<string, string>,\n name: string,\n): string | undefined {\n const expected = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected) return value\n }\n return undefined\n}\n","import { Command } from 'commander'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nexport interface ListFlags {\n limit?: string | number\n}\n\nexport interface ListDeps {\n terminal?: TerminalUI\n}\n\nfunction parseLimit(limit: string | number | undefined): number {\n const raw = limit\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid limit \"${raw}\". Expected a positive integer.`)\n }\n\n return parsed\n}\n\nexport async function runList(flags: ListFlags, deps: ListDeps = {}): Promise<void> {\n const limit = parseLimit(flags.limit ?? '20')\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const events = storage.list(limit)\n terminal.printEventList(events)\n } finally {\n storage.close()\n }\n}\n\nexport const listCommand = new Command('list')\n .description('Show received webhook events')\n .option('-n, --limit <count>', 'Number of events to show', '20')\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runList(options, { terminal })\n } catch (error) {\n terminal.printError(error instanceof Error ? error.message : String(error))\n process.exitCode = 1\n }\n })\n","import { Command } from 'commander'\nimport { forwardEvent } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nconst DEFAULT_REPLAY_TARGET_URL = 'http://localhost:3000/webhook'\n\nexport interface ReplayFlags {\n to?: string\n}\n\nexport interface ReplayDeps {\n terminal?: TerminalUI\n}\n\nfunction parseTargetUrl(targetUrl: string | undefined): string {\n const raw = targetUrl ?? DEFAULT_REPLAY_TARGET_URL\n\n try {\n return new URL(raw).href\n } catch {\n throw new Error(`Invalid target URL \"${raw}\".`)\n }\n}\n\nexport async function runReplay(\n eventId: string,\n flags: ReplayFlags,\n deps: ReplayDeps = {},\n): Promise<void> {\n const targetUrl = parseTargetUrl(flags.to)\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n try {\n const result = await forwardEvent(targetUrl, event)\n const body = result.body.length <= 200 ? result.body : `${result.body.slice(0, 197)}...`\n\n terminal.printReplayResult({\n status: result.status,\n body,\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(`Failed to replay \"${eventId}\" to ${targetUrl}: ${message}`)\n }\n } finally {\n storage.close()\n }\n}\n\nexport const replayCommand = new Command('replay')\n .description('Replay a stored webhook event')\n .argument('<event-id>', 'ID of the event to replay')\n .option('--to <url>', 'Target URL to send the event to', DEFAULT_REPLAY_TARGET_URL)\n .action(async (eventId, options) => {\n const terminal = createTerminal()\n\n try {\n await runReplay(eventId, options, { terminal })\n } catch (error) {\n terminal.printError(error instanceof Error ? error.message : String(error))\n process.exitCode = 1\n }\n })\n"],"mappings":"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","names":["Command","path","require","crypto","crypto","Command","Command","Command","Command","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../src/errors.ts","../src/cli/listen.ts","../src/server/index.ts","../src/storage/index.ts","../src/types.ts","../src/ui/terminal.ts","../src/verify/github.ts","../src/verify/headers.ts","../src/verify/stripe.ts","../src/cli/list.ts","../src/cli/replay.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { listenCommand } from './listen.js'\nimport { listCommand } from './list.js'\nimport { replayCommand } from './replay.js'\n\nconst program = new Command()\n\nprogram\n .name('hooklens')\n .description('Inspect, verify, and replay webhooks from your terminal')\n .version('0.1.0')\n\nprogram.addCommand(listenCommand)\nprogram.addCommand(listCommand)\nprogram.addCommand(replayCommand)\n\ntry {\n await program.parseAsync(process.argv)\n} catch (error) {\n console.error(errorMessage(error))\n process.exitCode = 1\n}\n","export function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function errorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createServer, type Server } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport type { VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { createGitHubVerifier } from '../verify/github.js'\nimport { createStripeVerifier } from '../verify/stripe.js'\n\nexport interface ListenFlags {\n port?: string | number\n verify?: string\n secret?: string\n forwardTo?: string\n}\n\nexport interface SignalBus {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n off(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n}\n\nexport interface ListenDeps {\n signals?: SignalBus\n terminal?: TerminalUI\n}\n\nfunction parsePort(port: string | number | undefined): number {\n const raw = port\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {\n throw new Error(`Invalid port \"${raw}\". Expected an integer between 0 and 65535.`)\n }\n\n return parsed\n}\n\n/** Maps --verify flags to a Verifier. See CONTRIBUTING.md → Adding a provider. */\nexport function buildVerifier(flags: ListenFlags): Verifier | undefined {\n if (!flags.verify) return undefined\n\n switch (flags.verify) {\n case 'stripe': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify stripe is set')\n }\n return createStripeVerifier({ secret: flags.secret })\n }\n case 'github': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify github is set')\n }\n return createGitHubVerifier({ secret: flags.secret })\n }\n default:\n throw new Error(`Unknown --verify provider \"${flags.verify}\". Supported: stripe, github`)\n }\n}\n\nasync function stopServer(server: Server | null): Promise<void> {\n if (!server) return\n await server.stop()\n}\n\nfunction printEventCapturedBestEffort(\n terminal: TerminalUI,\n event: WebhookEvent,\n result: VerificationResult | null,\n): void {\n try {\n terminal.printEventCaptured(event, result)\n } catch (error) {\n console.error(`Failed to print captured event: ${errorMessage(error)}`)\n }\n}\n\nexport async function runListen(flags: ListenFlags, deps: ListenDeps = {}): Promise<void> {\n const port = parsePort(flags.port)\n const verifier = buildVerifier(flags)\n const dbPath = defaultDbPath()\n const signals = deps.signals ?? process\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(dbPath)\n\n let server: Server | null = null\n let cleanedUp = false\n let listenersAttached = false\n\n const cleanup = async (printStopped: boolean): Promise<void> => {\n if (cleanedUp) return\n cleanedUp = true\n if (listenersAttached) {\n signals.off('SIGINT', onSignal)\n signals.off('SIGTERM', onSignal)\n listenersAttached = false\n }\n\n let stopError: unknown = null\n\n try {\n await stopServer(server)\n } catch (error) {\n stopError = error\n } finally {\n storage.close()\n }\n\n if (stopError) {\n throw stopError\n }\n\n if (printStopped) {\n terminal.printListenStopped()\n }\n }\n\n let settle: (() => void) | null = null\n let fail: ((error: unknown) => void) | null = null\n const shutdown = new Promise<void>((resolve, reject) => {\n settle = resolve\n fail = reject\n })\n\n const onSignal = () => {\n void cleanup(true).then(\n () => settle?.(),\n (error) => fail?.(error),\n )\n }\n\n try {\n server = createServer({\n port,\n storage,\n verifier,\n forwardTo: flags.forwardTo,\n onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),\n onForwardError: (event, error) => terminal.printForwardError(event.id, error.message),\n })\n\n signals.on('SIGINT', onSignal)\n signals.on('SIGTERM', onSignal)\n listenersAttached = true\n\n await server.start()\n\n if (cleanedUp) {\n return await shutdown\n }\n\n terminal.printListenStarted({\n port: server.port,\n dbPath,\n verifier: verifier?.provider,\n forwardTo: flags.forwardTo,\n })\n\n return await shutdown\n } catch (error) {\n if (cleanedUp) {\n return await shutdown\n }\n\n await cleanup(false)\n throw error\n }\n}\n\nexport const listenCommand = new Command('listen')\n .description('Start receiving webhooks')\n .option('-p, --port <port>', 'Port to listen on', '4400')\n .option('--verify <provider>', 'Verify signatures (stripe, github)')\n .option('--secret <secret>', 'Webhook signing secret')\n .option('--forward-to <url>', 'Forward received webhooks to this URL')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens listen\n hooklens listen -p 8080 --forward-to http://localhost:3000/webhook\n hooklens listen --verify stripe --secret whsec_xxx\n hooklens listen --verify github --secret ghsecret_xxx`,\n )\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runListen(options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n\n process.exitCode = 1\n }\n })\n","import http from 'node:http'\nimport crypto from 'node:crypto'\nimport { errorMessage, toError } from '../errors.js'\nimport type { ReplayResult, VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport type { createStorage } from '../storage/index.js'\n\ntype Storage = ReturnType<typeof createStorage>\n\nexport interface ServerOptions {\n port: number\n storage: Storage\n verifier?: Verifier\n forwardTo?: string\n forwardTimeoutMs?: number\n maxBodyBytes?: number\n onEvent?: (event: WebhookEvent, result: VerificationResult | null) => void\n onForwardError?: (event: WebhookEvent, error: Error) => void\n}\n\nexport interface Server {\n readonly port: number\n start(): Promise<void>\n stop(): Promise<void>\n}\n\n// Headers we strip before forwarding. This is the RFC 7230 section 6.1\n// hop-by-hop list plus host (fetch sets this from the destination URL) and\n// content-length (fetch recomputes this from the body).\nconst FORWARD_STRIP = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'host',\n 'content-length',\n])\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst DEFAULT_FORWARD_TIMEOUT_MS = 5000\n\nfunction forwardedStripSet(headers: Record<string, string>): Set<string> {\n const strip = new Set(FORWARD_STRIP)\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== 'connection') continue\n for (const token of value.split(/[,\\s]+/)) {\n const name = token.trim().toLowerCase()\n if (name) strip.add(name)\n }\n }\n return strip\n}\n\nfunction generateEventId(): string {\n return `evt_${crypto.randomBytes(12).toString('base64url')}`\n}\n\nclass PayloadTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`payload too large: max ${maxBytes} bytes`)\n this.name = 'PayloadTooLargeError'\n }\n}\n\nfunction isPayloadTooLargeError(error: unknown): error is PayloadTooLargeError {\n return error instanceof PayloadTooLargeError\n}\n\nfunction requestSockets(req: http.IncomingMessage): NodeJS.EventEmitter[] {\n const sockets = new Set<NodeJS.EventEmitter>()\n sockets.add(req.socket)\n const proxiedSocket = (req.socket as typeof req.socket & { proxy?: NodeJS.EventEmitter | null })\n .proxy\n if (proxiedSocket) sockets.add(proxiedSocket)\n return [...sockets]\n}\n\nexport function readBody(req: http.IncomingMessage, maxBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalBytes = 0\n let settled = false\n const sockets = requestSockets(req)\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onError)\n for (const socket of sockets) {\n socket.off('close', onSocketClose)\n socket.off('error', onSocketError)\n }\n }\n\n const rejectOnce = (error: Error) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n }\n\n const resolveOnce = (body: string) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(body)\n }\n\n const onData = (chunk: Buffer) => {\n totalBytes += chunk.length\n if (totalBytes > maxBytes) {\n req.resume()\n rejectOnce(new PayloadTooLargeError(maxBytes))\n return\n }\n chunks.push(chunk)\n }\n\n const onEnd = () => resolveOnce(Buffer.concat(chunks, totalBytes).toString('utf8'))\n const onError = (error: Error) => rejectOnce(error)\n const onSocketClose = () => rejectOnce(new Error('socket closed during request body'))\n const onSocketError = (error: Error) => rejectOnce(error)\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onError)\n for (const socket of sockets) {\n socket.on('close', onSocketClose)\n socket.on('error', onSocketError)\n }\n })\n}\n\nfunction headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\nexport function headersForForwarding(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {}\n const strip = forwardedStripSet(headers)\n for (const [key, value] of Object.entries(headers)) {\n if (!strip.has(key.toLowerCase())) out[key] = value\n }\n return out\n}\n\ninterface ParsedEventPath {\n pathname: string\n search: string\n}\n\nfunction forwardPathname(targetPathname: string, incomingPathname: string): string {\n if (targetPathname === '/' || targetPathname === '') return incomingPathname || '/'\n if (incomingPathname === '/' || incomingPathname === '') return targetPathname\n const base = targetPathname.endsWith('/') ? targetPathname.slice(0, -1) : targetPathname\n const incoming = incomingPathname.startsWith('/') ? incomingPathname : `/${incomingPathname}`\n return `${base}${incoming}`\n}\n\nexport function parseEventPath(path: string): ParsedEventPath {\n if (/^[A-Za-z][A-Za-z\\d+.-]*:/.test(path)) {\n const parsed = new URL(path)\n return { pathname: parsed.pathname, search: parsed.search }\n }\n\n const queryIndex = path.indexOf('?')\n if (queryIndex === -1) {\n return { pathname: path, search: '' }\n }\n\n return {\n pathname: path.slice(0, queryIndex),\n search: path.slice(queryIndex),\n }\n}\n\nfunction mergeForwardSearch(targetSearch: string, incomingSearch: string): string {\n const merged = new URLSearchParams(incomingSearch)\n const trusted = new URLSearchParams(targetSearch)\n\n for (const key of new Set(trusted.keys())) {\n merged.delete(key)\n }\n for (const [key, value] of trusted) {\n merged.append(key, value)\n }\n\n const search = merged.toString()\n return search.length > 0 ? `?${search}` : ''\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nexport async function forwardEvent(\n targetUrl: string,\n event: WebhookEvent,\n timeoutMs = DEFAULT_FORWARD_TIMEOUT_MS,\n): Promise<ReplayResult> {\n const target = new URL(targetUrl)\n const destination = new URL(target.href)\n const parsedEventPath = parseEventPath(event.path)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n\n destination.pathname = forwardPathname(destination.pathname, parsedEventPath.pathname)\n destination.search = mergeForwardSearch(destination.search, parsedEventPath.search)\n\n try {\n const hasBody = event.method !== 'GET' && event.method !== 'HEAD'\n const response = await fetch(destination, {\n method: event.method,\n headers: headersForForwarding(event.headers),\n body: hasBody ? event.body : undefined,\n signal: controller.signal,\n })\n\n return {\n status: response.status,\n body: await response.text(),\n }\n } catch (error) {\n if (isAbortError(error)) {\n throw new Error(`forward timed out after ${timeoutMs}ms`)\n }\n // fetch() wraps the real error (e.g. ECONNREFUSED) inside error.cause.\n // AggregateError (localhost resolving to both IPv6 and IPv4) has an empty\n // message but a useful code property. Fall back through each layer.\n const cause = error instanceof Error ? error.cause : undefined\n const code = cause instanceof Error ? (cause as NodeJS.ErrnoException).code : undefined\n const message = cause instanceof Error && cause.message ? cause.message : code\n throw new Error(message ?? errorMessage(error))\n } finally {\n clearTimeout(timeout)\n }\n}\n\nexport function createServer(opts: ServerOptions): Server {\n let boundPort = opts.port\n let httpServer: http.Server | null = null\n let isStarting = false\n const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const forwardTimeoutMs = opts.forwardTimeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS\n\n const handleRequest = async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> => {\n const body = await readBody(req, maxBodyBytes)\n\n const event: WebhookEvent = {\n id: generateEventId(),\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: headersToRecord(req.headers),\n body,\n }\n\n opts.storage.save(event)\n const verification = opts.verifier?.verify({ headers: event.headers, body: event.body }) ?? null\n opts.onEvent?.(event, verification)\n\n if (!opts.forwardTo) {\n res.statusCode = 200\n res.end('ok')\n return\n }\n\n try {\n const forwarded = await forwardEvent(opts.forwardTo, event, forwardTimeoutMs)\n res.statusCode = forwarded.status\n res.end(forwarded.body)\n } catch (error) {\n const err = toError(error)\n try {\n opts.onForwardError?.(event, err)\n } catch {\n // Don't let a broken callback turn a 502 into a 500.\n }\n res.statusCode = 502\n res.end(`bad gateway: ${err.message}`)\n }\n }\n\n return {\n get port() {\n return boundPort\n },\n\n async start() {\n if (httpServer || isStarting) {\n throw new Error('server already started')\n }\n\n isStarting = true\n const server = http.createServer((req, res) => {\n handleRequest(req, res).catch((err: unknown) => {\n if (isPayloadTooLargeError(err)) {\n res.statusCode = 413\n res.end(err.message)\n return\n }\n res.statusCode = 500\n res.end(errorMessage(err))\n })\n })\n httpServer = server\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => {\n server.off('error', onError)\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n reject(err)\n }\n\n server.once('error', onError)\n server.listen(opts.port, '127.0.0.1', () => {\n server.off('error', onError)\n const addr = server.address()\n if (addr && typeof addr !== 'string') {\n boundPort = addr.port\n }\n isStarting = false\n resolve()\n })\n })\n } catch (err) {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n throw err\n }\n },\n\n async stop() {\n if (!httpServer) return\n const server = httpServer\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n if (err) {\n reject(err)\n return\n }\n resolve()\n })\n })\n },\n }\n}\n","import os from 'node:os'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport type * as sqlite from 'node:sqlite'\nimport { eventRowSchema, webhookEventSchema, type EventRow, type WebhookEvent } from '../types.js'\n\nconst require = createRequire(import.meta.url)\n\n// tsup/esbuild currently rewrites a static `node:sqlite` import to `sqlite`,\n// which breaks the built CLI. Resolve it at runtime so the core module specifier\n// survives the bundle unchanged.\nconst { DatabaseSync } = require('node:' + 'sqlite') as typeof sqlite\n\nexport function defaultDbPath(): string {\n return path.join(os.homedir(), '.hooklens', 'events.db')\n}\n\nfunction rowToEvent(row: EventRow): WebhookEvent {\n return webhookEventSchema.parse({\n id: row.id,\n timestamp: row.timestamp,\n method: row.method,\n path: row.path,\n headers: JSON.parse(row.headers),\n body: row.body,\n })\n}\n\nexport function createStorage(dbPath: string) {\n fs.mkdirSync(path.dirname(dbPath), { recursive: true })\n const db = new DatabaseSync(dbPath)\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n method TEXT NOT NULL,\n path TEXT NOT NULL,\n headers TEXT NOT NULL,\n body TEXT NOT NULL\n )\n `)\n\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO events (id, timestamp, method, path, headers, body)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n\n const getStmt = db.prepare(`SELECT * FROM events WHERE id = ?`)\n const listAllStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC`)\n const listLimitedStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`)\n const clearStmt = db.prepare(`DELETE FROM events`)\n\n return {\n save(event: WebhookEvent): void {\n insertStmt.run(\n event.id,\n event.timestamp,\n event.method,\n event.path,\n JSON.stringify(event.headers),\n event.body,\n )\n },\n\n load(id: string): WebhookEvent | null {\n const raw = getStmt.get(id)\n if (!raw) return null\n const row = eventRowSchema.parse(raw)\n return rowToEvent(row)\n },\n\n list(limit?: number): WebhookEvent[] {\n if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {\n throw new Error(`Invalid limit: must be a positive integer, got ${limit}`)\n }\n const raw = limit === undefined ? listAllStmt.all() : listLimitedStmt.all(limit)\n const rows = raw.map((r) => eventRowSchema.parse(r))\n return rows.map(rowToEvent)\n },\n\n clear(): void {\n clearStmt.run()\n },\n\n close(): void {\n db.close()\n },\n }\n}\n","import { z } from 'zod'\n\n// A webhook event as it lives in memory and is exposed to the rest of the app.\n// Headers are a parsed object here -- on disk they're stored as a JSON string.\nexport const webhookEventSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.record(z.string(), z.string()),\n body: z.string(),\n})\n\nexport type WebhookEvent = z.infer<typeof webhookEventSchema>\n\n// The shape of a row read directly from the SQLite events table.\n// headers is a JSON string at this layer; rowToEvent parses it.\nexport const eventRowSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.string(),\n body: z.string(),\n})\n\nexport type EventRow = z.infer<typeof eventRowSchema>\n\nexport const verificationResultSchema = z.object({\n valid: z.boolean(),\n provider: z.string(),\n message: z.string(),\n code: z.enum([\n 'valid',\n 'missing_header',\n 'malformed_header',\n 'expired_timestamp',\n 'signature_mismatch',\n 'body_mutated',\n ]),\n})\n\nexport type VerificationResult = z.infer<typeof verificationResultSchema>\n\nexport const replayResultSchema = z.object({\n status: z.number().int(),\n body: z.string(),\n})\n\nexport type ReplayResult = z.infer<typeof replayResultSchema>\n\n/** Provider signature verifier. See CONTRIBUTING.md → Adding a provider. */\nexport interface Verifier {\n readonly provider: string\n verify(event: { headers: Record<string, string>; body: string }): VerificationResult\n}\n","import chalk from 'chalk'\nimport type { ReplayResult, VerificationResult, WebhookEvent } from '../types.js'\n\nexport interface ListenStartedInfo {\n port: number\n dbPath: string\n verifier?: string\n forwardTo?: string\n}\n\nexport interface TerminalUI {\n printListenStarted(info: ListenStartedInfo): void\n printEventCaptured(event: WebhookEvent, result: VerificationResult | null): void\n printForwardError(eventId: string, reason: string): void\n printEventList(events: WebhookEvent[]): void\n printReplayResult(result: ReplayResult): void\n printListenStopped(): void\n printError(message: string): void\n}\n\nfunction writeLine(stream: NodeJS.WriteStream, line: string): void {\n stream.write(`${line}\\n`)\n}\n\nfunction verificationLabel(result: VerificationResult | null): string {\n if (!result) return chalk.cyan('RECV')\n return result.valid ? chalk.green('PASS') : chalk.red('FAIL')\n}\n\nexport function createTerminal(\n stdout: NodeJS.WriteStream = process.stdout,\n stderr: NodeJS.WriteStream = process.stderr,\n): TerminalUI {\n return {\n printListenStarted(info) {\n writeLine(\n stdout,\n `${chalk.bold('Listening on')} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`,\n )\n\n writeLine(stdout, `Verifier: ${info.verifier ?? 'none'}`)\n writeLine(stdout, `Forwarding to: ${info.forwardTo ?? 'disabled'}`)\n writeLine(stdout, `Storage: ${info.dbPath}`)\n },\n\n printEventCaptured(event, result) {\n const label = verificationLabel(result)\n const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`\n\n if (!result) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.message}`)\n },\n\n printForwardError(eventId, reason) {\n writeLine(stdout, `${chalk.red('FWD')} ${chalk.bold(eventId)} ${reason}`)\n },\n\n printEventList(events) {\n if (!events.length) {\n writeLine(stdout, chalk.dim('No stored events.'))\n return\n }\n\n for (const event of events) {\n const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`\n writeLine(stdout, row)\n }\n },\n\n printReplayResult(result) {\n const summary = `${chalk.bold('Replay response:')} ${chalk.cyan(String(result.status))}`\n\n if (!result.body) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.body}`)\n },\n\n printListenStopped() {\n writeLine(stdout, chalk.dim('Stopped listening.'))\n },\n\n printError(message) {\n writeLine(stderr, chalk.red(message))\n },\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyGitHubOptions {\n payload: string\n header: string | null | undefined\n secret: string\n}\n\nconst PROVIDER = 'github'\nconst PREFIX = 'sha256='\nconst SHA256_HEX = /^[0-9a-fA-F]{64}$/\n\nfunction computeHmac(secret: string, payload: string): string {\n return crypto.createHmac('sha256', secret).update(payload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, actual: string): boolean {\n if (expected.length !== actual.length) return false\n const expectedBuf = Buffer.from(expected, 'utf8')\n const actualBuf = Buffer.from(actual, 'utf8')\n return crypto.timingSafeEqual(expectedBuf, actualBuf)\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyGitHubSignature(opts: VerifyGitHubOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'x-hub-signature-256 header not found. Is this actually from GitHub?',\n )\n }\n\n if (!opts.header.startsWith(PREFIX)) {\n return failure('malformed_header', 'x-hub-signature-256 header must start with sha256=')\n }\n\n const signature = opts.header.slice(PREFIX.length)\n if (signature.length === 0) {\n return failure('malformed_header', 'x-hub-signature-256 header has no signature after sha256=')\n }\n\n if (!SHA256_HEX.test(signature)) {\n return failure('malformed_header', 'x-hub-signature-256 header has invalid sha256 hex digest')\n }\n\n const normalizedSignature = signature.toLowerCase()\n const expected = computeHmac(opts.secret, opts.payload)\n\n if (constantTimeMatch(expected, normalizedSignature)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, canonical)\n if (constantTimeMatch(expectedCanonical, normalizedSignature)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the GitHub settings.',\n )\n}\n\nexport function createGitHubVerifier(opts: { secret: string }): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyGitHubSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'x-hub-signature-256'),\n secret: opts.secret,\n }),\n }\n}\n","export function getHeaderCaseInsensitive(\n headers: Record<string, string>,\n name: string,\n): string | undefined {\n const expected = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected) return value\n }\n return undefined\n}\n\nexport function tryCanonicalForm(payload: string): string | null {\n try {\n const canonical = JSON.stringify(JSON.parse(payload))\n return canonical === payload ? null : canonical\n } catch {\n return null\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyStripeOptions {\n payload: string\n header: string | null | undefined\n secret: string\n tolerance?: number\n now?: () => number\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\nconst PROVIDER = 'stripe'\n\ninterface ParsedHeader {\n timestamp: number\n signatures: string[]\n}\n\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp: number | null = null\n const signatures: string[] = []\n\n for (const part of header.split(',')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx === -1) return null\n\n const key = part.slice(0, eqIdx)\n const value = part.slice(eqIdx + 1)\n\n if (key === 't') {\n if (!/^\\d+$/.test(value)) return null\n timestamp = Number(value)\n } else if (key === 'v1') {\n if (value.length === 0) return null\n signatures.push(value)\n }\n }\n\n if (timestamp === null || signatures.length === 0) return null\n return { timestamp, signatures }\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return crypto.createHmac('sha256', secret).update(signedPayload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, candidates: string[]): boolean {\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const candidate of candidates) {\n if (candidate.length !== expected.length) continue\n const candidateBuf = Buffer.from(candidate, 'utf8')\n if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) return true\n }\n return false\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyStripeSignature(opts: VerifyStripeOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'stripe-signature header not found. Is this actually from Stripe?',\n )\n }\n\n const parsed = parseHeader(opts.header)\n if (!parsed) {\n return failure(\n 'malformed_header',\n 'stripe-signature header is malformed. Expected format: t=timestamp,v1=signature',\n )\n }\n\n const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n const nowMs = (opts.now ?? Date.now)()\n const ageSeconds = Math.floor(nowMs / 1000) - parsed.timestamp\n\n if (ageSeconds > tolerance) {\n const minutes = Math.floor(ageSeconds / 60)\n return failure(\n 'expired_timestamp',\n `Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`,\n )\n }\n\n const signedPayload = `${parsed.timestamp}.${opts.payload}`\n const expected = computeHmac(opts.secret, signedPayload)\n\n if (constantTimeMatch(expected, parsed.signatures)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, `${parsed.timestamp}.${canonical}`)\n if (constantTimeMatch(expectedCanonical, parsed.signatures)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the Stripe dashboard.',\n )\n}\n\nexport interface StripeVerifierOptions {\n secret: string\n tolerance?: number\n}\n\nexport function createStripeVerifier(opts: StripeVerifierOptions): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyStripeSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'stripe-signature'),\n secret: opts.secret,\n tolerance: opts.tolerance,\n }),\n }\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nexport interface ListFlags {\n limit?: string | number\n}\n\nexport interface ListDeps {\n terminal?: TerminalUI\n}\n\nfunction parseLimit(limit: string | number | undefined): number {\n const raw = limit\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid limit \"${raw}\". Expected a positive integer.`)\n }\n\n return parsed\n}\n\nexport async function runList(flags: ListFlags, deps: ListDeps = {}): Promise<void> {\n const limit = parseLimit(flags.limit ?? '20')\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const events = storage.list(limit)\n terminal.printEventList(events)\n } finally {\n storage.close()\n }\n}\n\nexport const listCommand = new Command('list')\n .description('Show received webhook events')\n .option('-n, --limit <count>', 'Number of events to show', '20')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens list\n hooklens list -n 5`,\n )\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runList(options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { forwardEvent } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nconst DEFAULT_REPLAY_TARGET_URL = 'http://localhost:3000/webhook'\n\nexport interface ReplayFlags {\n to?: string\n}\n\nexport interface ReplayDeps {\n terminal?: TerminalUI\n}\n\nfunction parseTargetUrl(targetUrl: string | undefined): string {\n const raw = targetUrl ?? DEFAULT_REPLAY_TARGET_URL\n\n try {\n return new URL(raw).href\n } catch {\n throw new Error(`Invalid target URL \"${raw}\".`)\n }\n}\n\nexport async function runReplay(\n eventId: string,\n flags: ReplayFlags,\n deps: ReplayDeps = {},\n): Promise<void> {\n const targetUrl = parseTargetUrl(flags.to)\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n try {\n const result = await forwardEvent(targetUrl, event)\n const body = result.body.length <= 200 ? result.body : `${result.body.slice(0, 197)}...`\n\n terminal.printReplayResult({\n status: result.status,\n body,\n })\n } catch (error) {\n throw new Error(`Failed to replay \"${eventId}\" to ${targetUrl}: ${errorMessage(error)}`)\n }\n } finally {\n storage.close()\n }\n}\n\nexport const replayCommand = new Command('replay')\n .description('Replay a stored webhook event')\n .argument('<event-id>', 'ID of the event to replay')\n .option('--to <url>', 'Target URL to send the event to', DEFAULT_REPLAY_TARGET_URL)\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens replay evt_abc123\n hooklens replay evt_abc123 --to http://localhost:8080/hook`,\n )\n .action(async (eventId, options) => {\n const terminal = createTerminal()\n\n try {\n await runReplay(eventId, options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n"],"mappings":"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","names":["Command","path","require","crypto","crypto","crypto","PROVIDER","computeHmac","crypto","constantTimeMatch","success","failure","Command","Command","Command","Command","Command"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hooklens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Inspect, verify, and replay webhooks from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"LICENSE"
|
|
50
50
|
],
|
|
51
51
|
"devDependencies": {
|
|
52
|
+
"@octokit/webhooks-methods": "^6.0.0",
|
|
52
53
|
"@types/node": "^22.0.0",
|
|
53
54
|
"eslint": "^9.0.0",
|
|
54
55
|
"husky": "^9.1.7",
|