diff --git a/backend/src/lib/ip/index.test.ts b/backend/src/lib/ip/index.test.ts deleted file mode 100644 index 5f3b64fac..000000000 --- a/backend/src/lib/ip/index.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { extractIPDetails, IPType, isValidCidr, isValidIp, isValidIpOrCidr } from "./index"; - -describe("IP Validation", () => { - describe("isValidIp", () => { - test("should validate IPv4 addresses with ports", () => { - expect(isValidIp("192.168.1.1:8080")).toBe(true); - expect(isValidIp("10.0.0.1:1234")).toBe(true); - expect(isValidIp("172.16.0.1:80")).toBe(true); - }); - - test("should validate IPv6 addresses with ports", () => { - expect(isValidIp("[2001:db8::1]:8080")).toBe(true); - expect(isValidIp("[fe80::1ff:fe23:4567:890a]:1234")).toBe(true); - expect(isValidIp("[::1]:80")).toBe(true); - }); - - test("should validate regular IPv4 addresses", () => { - expect(isValidIp("192.168.1.1")).toBe(true); - expect(isValidIp("10.0.0.1")).toBe(true); - expect(isValidIp("172.16.0.1")).toBe(true); - }); - - test("should validate regular IPv6 addresses", () => { - expect(isValidIp("2001:db8::1")).toBe(true); - expect(isValidIp("fe80::1ff:fe23:4567:890a")).toBe(true); - expect(isValidIp("::1")).toBe(true); - }); - - test("should reject invalid IP addresses", () => { - expect(isValidIp("256.256.256.256")).toBe(false); - expect(isValidIp("192.168.1")).toBe(false); - expect(isValidIp("192.168.1.1.1")).toBe(false); - expect(isValidIp("2001:db8::1::1")).toBe(false); - expect(isValidIp("invalid")).toBe(false); - }); - - test("should reject malformed IP addresses with ports", () => { - expect(isValidIp("192.168.1.1:")).toBe(false); - expect(isValidIp("192.168.1.1:abc")).toBe(false); - expect(isValidIp("[2001:db8::1]")).toBe(false); - expect(isValidIp("[2001:db8::1]:")).toBe(false); - expect(isValidIp("[2001:db8::1]:abc")).toBe(false); - }); - }); - - describe("isValidCidr", () => { - test("should validate IPv4 CIDR blocks", () => { - expect(isValidCidr("192.168.1.0/24")).toBe(true); - expect(isValidCidr("10.0.0.0/8")).toBe(true); - expect(isValidCidr("172.16.0.0/16")).toBe(true); - }); - - test("should validate IPv6 CIDR blocks", () => { - expect(isValidCidr("2001:db8::/32")).toBe(true); - expect(isValidCidr("fe80::/10")).toBe(true); - expect(isValidCidr("::/0")).toBe(true); - }); - - test("should reject invalid CIDR blocks", () => { - expect(isValidCidr("192.168.1.0/33")).toBe(false); - expect(isValidCidr("2001:db8::/129")).toBe(false); - expect(isValidCidr("192.168.1.0/abc")).toBe(false); - expect(isValidCidr("invalid/24")).toBe(false); - }); - }); - - describe("isValidIpOrCidr", () => { - test("should validate both IP addresses and CIDR blocks", () => { - expect(isValidIpOrCidr("192.168.1.1")).toBe(true); - expect(isValidIpOrCidr("2001:db8::1")).toBe(true); - expect(isValidIpOrCidr("192.168.1.0/24")).toBe(true); - expect(isValidIpOrCidr("2001:db8::/32")).toBe(true); - }); - - test("should reject invalid inputs", () => { - expect(isValidIpOrCidr("invalid")).toBe(false); - expect(isValidIpOrCidr("192.168.1.0/33")).toBe(false); - expect(isValidIpOrCidr("2001:db8::/129")).toBe(false); - }); - }); - - describe("extractIPDetails", () => { - test("should extract IPv4 address details", () => { - const result = extractIPDetails("192.168.1.1"); - expect(result).toEqual({ - ipAddress: "192.168.1.1", - type: IPType.IPV4 - }); - }); - - test("should extract IPv6 address details", () => { - const result = extractIPDetails("2001:db8::1"); - expect(result).toEqual({ - ipAddress: "2001:db8::1", - type: IPType.IPV6 - }); - }); - - test("should extract IPv4 CIDR details", () => { - const result = extractIPDetails("192.168.1.0/24"); - expect(result).toEqual({ - ipAddress: "192.168.1.0", - type: IPType.IPV4, - prefix: 24 - }); - }); - - test("should extract IPv6 CIDR details", () => { - const result = extractIPDetails("2001:db8::/32"); - expect(result).toEqual({ - ipAddress: "2001:db8::", - type: IPType.IPV6, - prefix: 32 - }); - }); - - test("should throw error for invalid IP", () => { - expect(() => extractIPDetails("invalid")).toThrow("Failed to extract IP details"); - }); - }); -}); diff --git a/backend/src/lib/ip/index.ts b/backend/src/lib/ip/index.ts index 9b594a583..0b35a2759 100644 --- a/backend/src/lib/ip/index.ts +++ b/backend/src/lib/ip/index.ts @@ -1,7 +1,5 @@ import net from "node:net"; -import RE2 from "re2"; - import { ForbiddenRequestError } from "../errors"; export enum IPType { @@ -9,55 +7,25 @@ export enum IPType { IPV6 = "ipv6" } -const PORT_REGEX = new RE2(/^\d+$/); - -/** - * Strips port from IP address if present. - * Handles both IPv4 (e.g. 1.2.3.4:1234) and IPv6 (e.g. [2001:db8::1]:8080) formats. - * Returns the IP address without port and a boolean indicating if a port was present. - */ -const stripPort = (ip: string): { ipAddress: string } => { - // Handle IPv6 with port (e.g. [2001:db8::1]:8080) - if (ip.startsWith("[") && ip.includes("]:")) { - const endBracketIndex = ip.indexOf("]"); - if (endBracketIndex === -1) return { ipAddress: ip }; - const ipPart = ip.slice(1, endBracketIndex); - const portPart = ip.slice(endBracketIndex + 2); - if (!portPart || !PORT_REGEX.test(portPart)) return { ipAddress: ip }; - return { ipAddress: ipPart }; - } - - // Handle IPv4 with port (e.g. 1.2.3.4:1234) - if (ip.includes(":")) { - const [ipPart, portPart] = ip.split(":"); - if (!portPart || !PORT_REGEX.test(portPart)) return { ipAddress: ip }; - return { ipAddress: ipPart }; - } - - return { ipAddress: ip }; -}; - /** * Return details of IP [ip]: * - If [ip] is a specific IP address then return the IPv4/IPv6 address * - If [ip] is a subnet then return the network IPv4/IPv6 address and prefix */ export const extractIPDetails = (ip: string) => { - const { ipAddress } = stripPort(ip); - - if (net.isIPv4(ipAddress)) + if (net.isIPv4(ip)) return { - ipAddress, + ipAddress: ip, type: IPType.IPV4 }; - if (net.isIPv6(ipAddress)) + if (net.isIPv6(ip)) return { - ipAddress, + ipAddress: ip, type: IPType.IPV6 }; - const [ipNet, prefix] = ipAddress.split("/"); + const [ipNet, prefix] = ip.split("/"); let type; switch (net.isIP(ipNet)) { @@ -89,8 +57,7 @@ export const extractIPDetails = (ip: string) => { * */ export const isValidCidr = (cidr: string): boolean => { - const { ipAddress } = stripPort(cidr); - const [ip, prefix] = ipAddress.split("/"); + const [ip, prefix] = cidr.split("/"); const prefixNum = parseInt(prefix, 10); @@ -123,15 +90,13 @@ export const isValidCidr = (cidr: string): boolean => { * */ export const isValidIpOrCidr = (ip: string): boolean => { - const { ipAddress } = stripPort(ip); - // if the string contains a slash, treat it as a CIDR block - if (ipAddress.includes("/")) { - return isValidCidr(ipAddress); + if (ip.includes("/")) { + return isValidCidr(ip); } // otherwise, treat it as a standalone IP address - if (net.isIPv4(ipAddress) || net.isIPv6(ipAddress)) { + if (net.isIPv4(ip) || net.isIPv6(ip)) { return true; } @@ -139,8 +104,7 @@ export const isValidIpOrCidr = (ip: string): boolean => { }; export const isValidIp = (ip: string) => { - const { ipAddress } = stripPort(ip); - return net.isIPv4(ipAddress) || net.isIPv6(ipAddress); + return net.isIPv4(ip) || net.isIPv6(ip); }; export type TIp = { @@ -148,7 +112,6 @@ export type TIp = { type: IPType; prefix: number; }; - /** * Validates the IP address [ipAddress] against the trusted IPs [trustedIps]. */ @@ -163,9 +126,8 @@ export const checkIPAgainstBlocklist = ({ ipAddress, trustedIps }: { ipAddress: } } - const { type, ipAddress: cleanIpAddress } = extractIPDetails(ipAddress); - - const check = blockList.check(cleanIpAddress, type); + const { type } = extractIPDetails(ipAddress); + const check = blockList.check(ipAddress, type); if (!check) throw new ForbiddenRequestError({