From e81ee78e86017e56de62d7f71557332107638db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Ramos?= <47223013+jramsgz@users.noreply.github.com> Date: Wed, 29 Mar 2023 21:56:45 +0000 Subject: [PATCH] Add initial scopes support --- src/utils/config/scope.js | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/utils/config/scope.js diff --git a/src/utils/config/scope.js b/src/utils/config/scope.js new file mode 100644 index 00000000..ccf08152 --- /dev/null +++ b/src/utils/config/scope.js @@ -0,0 +1,83 @@ +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; + } + } 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 { + console.error("Invalid ipSpace: ", ipSpace); + } + return false; +} + +function isRequestProxied(req) { + 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 = req.connection.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; +} + +export function getClientIP(req) { + // Check if the request is proxied + const proxied = isRequestProxied(req); + // If the request is proxied, get the forwarded IP address + // from the X-Real-IP header + const forwarded = req.headers["x-real-ip"]; + // If not get the connection IP address + const ip = req.connection.remoteAddress.replace(/^.*:/, ''); + + // Conditionally return the forwarded IP address or the connection IP address + return proxied ? (forwarded || ip) : ip; +} + +export function isInLocalScope(req) { + const settings = getSettings(); + // Check if localscope is set + const localScope = settings?.localscope; + + // If localscope is set, check if the client IP + // is in the localscope address space using CIDR notation. + if (localScope) { + const ip = getClientIP(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 false; +} + \ No newline at end of file