diff --git a/package.json b/package.json index af2cf68a..e685cf39 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dockerode": "^3.3.4", "follow-redirects": "^1.15.2", "i18next": "^21.9.2", + "ipaddr.js": "^2.0.1", "js-yaml": "^4.1.0", "json-rpc-2.0": "^1.4.1", "memory-cache": "^0.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f3f6adf..b54638d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,9 @@ dependencies: i18next: specifier: ^21.9.2 version: 21.10.0 + ipaddr.js: + specifier: ^2.0.1 + version: 2.0.1 js-yaml: specifier: ^4.1.0 version: 4.1.0 @@ -1924,6 +1927,11 @@ packages: engines: {node: '>= 0.10'} dev: false + /ipaddr.js@2.0.1: + resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==} + engines: {node: '>= 10'} + dev: false + /is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} diff --git a/src/utils/config/scope.js b/src/utils/config/scope.js index 81a613bd..27ec7d43 100644 --- a/src/utils/config/scope.js +++ b/src/utils/config/scope.js @@ -2,51 +2,46 @@ import { getClientIp } from "@supercharge/request-ip"; import { getSettings } from "utils/config/config"; -function checkIPRange(ip, ipSpace) { - // Check if the given IP is in the ipSpace address space using - // CIDR notation. If ipSpace is a plain IPv4 we just compare them. - const ipSpaceParts = ipSpace.split("/"); - if (ipSpaceParts.length === 1) { - if (ip === ipSpace) { - return true; +const ipaddr = require("ipaddr.js"); + +function checkSingleIPRange(ip, cidr) { + try { + const ipAddr = ipaddr.process(ip); + // If the CIDR is not in the format of x.x.x.x/y, we assume it is a single IP + if (!cidr.includes("/")) { + const cidrIP = ipaddr.process(cidr); + // If both are IPv6, we need to normalize the strings + if (ipAddr.kind() === "ipv6" && cidrIP.kind() === "ipv6") { + return (ipAddr.toNormalizedString() === cidrIP.toNormalizedString()); + } + return ipAddr.toString() === cidrIP.toString(); } - } else if (ipSpaceParts.length === 2) { - const ipParts = ip.split("."); - const ipNum = parseInt(ipParts[0], 10) * 256 * 256 * 256 + parseInt(ipParts[1], 10) * 256 * 256 + parseInt(ipParts[2], 10) * 256 + parseInt(ipParts[3], 10); - const ipSpaceNum = parseInt(ipSpaceParts[0].split(".")[0], 10) * 256 * 256 * 256 + parseInt(ipSpaceParts[0].split(".")[1], 10) * 256 * 256 + parseInt(ipSpaceParts[0].split(".")[2], 10) * 256 + parseInt(ipSpaceParts[0].split(".")[3], 10); - const mask = 32 - parseInt(ipSpaceParts[1], 10); - // eslint-disable-next-line no-bitwise - const maskNum = 0xffffffff << mask; - // eslint-disable-next-line no-bitwise - if ((ipNum & maskNum) === (ipSpaceNum & maskNum)) { - return true; - } - } else { - throw new Error(`Invalid ipSpace: ${ipSpace}`); + // Otherwise, we assume it is a CIDR range + const range = ipaddr.parseCIDR(cidr); + return ipAddr.match(range); + } catch (e) { + return false; + } +} + +function checkIPRange(ip, range) { + if (typeof range === "string") { + return checkSingleIPRange(ip, range); + } + if (Array.isArray(range)) { + return range.find((cidr) => checkSingleIPRange(ip, cidr)) !== undefined; } return false; } -function isRequestProxied(remoteAddress) { +export function isRequestProxied(remoteAddress) { const settings = getSettings(); // Check if trustedproxies is set const trustedProxies = settings?.trustedproxies; // If trustedproxies is set, check if the client IP - // is in the trustedproxies address space using CIDR notation. - if (trustedProxies) { - // Get the connection IP and strip IPv6 from the hybrid IPv4-IPv6 socket - const ip = remoteAddress.replace(/^.*:/, ''); - - for (let i = 0; i < trustedProxies.length; i += 1) { - const proxy = trustedProxies[i].trim(); - const inRange = checkIPRange(ip, proxy); - if (inRange) { - return true; - } - } - } - return false; + // is in the trustedproxies address space. + return trustedProxies ? checkIPRange(remoteAddress, trustedProxies) : false; } export function getRealClientIP(req) { @@ -63,17 +58,10 @@ export function isInLocalScope(req) { const localScope = settings?.localscope; // If localscope is set, check if the client IP - // is in the localscope address space using CIDR notation. + // is in the localscope address space. if (localScope) { const ip = getRealClientIP(req); - - for (let i = 0; i < localScope.length; i += 1) { - const localIP = localScope[i].trim(); - const inRange = checkIPRange(ip, localIP) - if (inRange) { - return true; - } - } + return checkIPRange(ip, localScope); } return false; }