wiwiway / src /lib /ipUtils.ts
Claude Code
Sync gateway codebase without binaries
116b4cb
Raw
History Blame Contribute Delete
1.89 kB
import { isIP } from "node:net";
/**
* T07: Extract the real client IP from X-Forwarded-For header.
* Skips invalid entries like "unknown" or empty strings.
* Falls back to remoteAddress if no valid IP found.
* Ref: sub2api PR #1135
*
* @param xForwardedFor - Value of the X-Forwarded-For header (may be CSV)
* @param remoteAddress - Fallback from the raw socket (req.socket.remoteAddress)
* @returns The first valid IP address found, or "unknown"
*/
export function extractClientIp(
xForwardedFor: string | null | undefined,
remoteAddress: string | undefined
): string {
if (xForwardedFor) {
const entries = xForwardedFor.split(",");
for (const entry of entries) {
const trimmed = entry.trim();
if (trimmed && isIP(trimmed) !== 0) {
return trimmed; // First valid IP wins
}
}
}
return remoteAddress?.trim() ?? "unknown";
}
/**
* Extract client IP from a Request or NextRequest object.
* Checks X-Forwarded-For, X-Real-IP, CF-Connecting-IP, then socket.
*/
export function getClientIpFromRequest(req: {
headers?: Headers | { get?: (n: string) => string | null };
socket?: { remoteAddress?: string };
ip?: string;
}): string {
// Helper to get header value from either Headers object or plain object
const getHeader = (name: string): string | null => {
if (!req.headers) return null;
if (typeof (req.headers as Headers).get === "function") {
return (req.headers as Headers).get(name);
}
return null;
};
// Priority: CF-Connecting-IP (Cloudflare) > X-Forwarded-For > X-Real-IP > socket
const cfIp = getHeader("cf-connecting-ip");
if (cfIp && isIP(cfIp.trim()) !== 0) return cfIp.trim();
const xff = getHeader("x-forwarded-for");
const realIp = getHeader("x-real-ip");
const remoteAddress = req.ip ?? req.socket?.remoteAddress;
return extractClientIp(xff ?? realIp, remoteAddress);
}