UNPKG

get-client-ip

Version:

📍 A Lightweight Utility for Extracting the Real Client IP Address from Incoming HTTP Requests

126 lines (123 loc) 4.09 kB
'use strict'; var net = require('net'); // src/index.ts function $isIP(ip) { return typeof ip === "string" && net.isIP(ip) !== 0; } function $isTrustedProxyAddress(ip) { const ipVersion = net.isIP(ip); if (ipVersion === 4) { const [a, b] = ip.split(".").map((part) => Number.parseInt(part, 10)); const secondOctet = b ?? -1; if (a === 10) return true; if (a === 127) return true; if (a === 169 && secondOctet === 254) return true; if (a === 192 && secondOctet === 168) return true; if (a === 172 && secondOctet >= 16 && secondOctet <= 31) return true; return false; } if (ipVersion === 6) { const lower = ip.toLowerCase(); return lower === "::1" || lower.startsWith("fe80:") || lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("::ffff:10.") || lower.startsWith("::ffff:127.") || lower.startsWith("::ffff:169.254.") || lower.startsWith("::ffff:192.168.") || /^::ffff:172\.(1[6-9]|2\d|3[0-1])\./.test(lower); } return false; } function $stripZoneId(ip) { const idx = ip.indexOf("%"); return idx === -1 ? ip : ip.slice(0, idx); } function $normalizeIpCandidate(candidate) { const trimmed = candidate.trim(); if (!trimmed) return null; const unquoted = trimmed.replace(/^"(.*)"$/, "$1"); const bracketed = unquoted.match(/^\[([^\]]+)\](?::\d+)?$/); if (bracketed?.[1]) { const ip = $stripZoneId(bracketed[1]); if (net.isIP(ip) !== 0) return ip; } const stripped = $stripZoneId(unquoted); if (net.isIP(stripped) !== 0) return stripped; const ipv4WithPort = unquoted.match(/^([^:]+):(\d+)$/); if (ipv4WithPort?.[1] && net.isIP(ipv4WithPort[1]) === 4) return ipv4WithPort[1]; return null; } function $extractForwarded(value) { const lines = Array.isArray(value) ? value : [value]; const ips = []; for (const line of lines) { for (const segment of line.split(",")) { for (const directive of segment.split(";")) { const [rawKey, rawVal] = directive.split("=", 2); if (!rawKey || !rawVal) continue; if (rawKey.trim().toLowerCase() !== "for") continue; const ip = $normalizeIpCandidate(rawVal); if (ip) ips.push(ip); } } } return ips.length > 0 ? ips : null; } function $extractHeaderIps(headerValue) { const values = Array.isArray(headerValue) ? headerValue : [headerValue]; const ips = []; for (const value of values) { for (const token of value.split(",")) { const ip = $normalizeIpCandidate(token); if (ip) ips.push(ip); } } return ips.length > 0 ? ips : null; } var LOOKUP_HEADERS = [ "cf-connecting-ip", "true-client-ip", "fastly-client-ip", "x-appengine-user-ip", "cf-pseudo-ipv4", "x-client-ip", "x-forwarded-for", "forwarded-for", "x-forwarded", "x-real-ip", "x-cluster-client-ip" ]; function $extractIpFromHeaders(req) { if ($isIP(req.ip)) return [req.ip]; if (!req.headers) return null; const remoteAddress = req.socket?.remoteAddress; if (!$isIP(remoteAddress) || !$isTrustedProxyAddress(remoteAddress)) { return null; } if (typeof req.headers.forwarded === "string" || Array.isArray(req.headers.forwarded)) { const forwardedIps = $extractForwarded(req.headers.forwarded); if (forwardedIps) return forwardedIps; } for (const header of LOOKUP_HEADERS) { const ip = req.headers[header]; if (!ip || !(typeof ip === "string" || Array.isArray(ip))) continue; const extractedIps = $extractHeaderIps(ip); if (extractedIps) return extractedIps; } return null; } function getClientIp(req, res, next) { if (!req) throw new Error("Request is undefined"); const ips = $extractIpFromHeaders(req); if (ips) { req.clientIp = ips[0]; req.clientIps = ips; next?.(); return ips[0]; } const remoteAddress = req.socket?.remoteAddress; if ($isIP(remoteAddress)) { req.clientIp = remoteAddress; req.clientIps = [remoteAddress]; next?.(); return remoteAddress; } next?.(); } exports.getClientIp = getClientIp; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map