get-client-ip
Version:
📍 A Lightweight Utility for Extracting the Real Client IP Address from Incoming HTTP Requests
124 lines (122 loc) • 4.04 kB
JavaScript
import { isIP } from 'net';
// src/index.ts
function $isIP(ip) {
return typeof ip === "string" && isIP(ip) !== 0;
}
function $isTrustedProxyAddress(ip) {
const ipVersion = 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 (isIP(ip) !== 0) return ip;
}
const stripped = $stripZoneId(unquoted);
if (isIP(stripped) !== 0) return stripped;
const ipv4WithPort = unquoted.match(/^([^:]+):(\d+)$/);
if (ipv4WithPort?.[1] && 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?.();
}
export { getClientIp };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map