From 437c168e6f28318c12c7823bd21e5823f3672449 Mon Sep 17 00:00:00 2001 From: journry789 Date: Mon, 15 Dec 2025 13:56:39 +0800 Subject: [PATCH] This resolves the issue of being unable to obtain the client's IP address due to the IPv6 format. --- src/lib/ip.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/src/lib/ip.ts b/src/lib/ip.ts index 5cd77574..a0e3a825 100644 --- a/src/lib/ip.ts +++ b/src/lib/ip.ts @@ -1,3 +1,5 @@ +import ipaddr from 'ipaddr.js'; + export const IP_ADDRESS_HEADERS = [ 'true-client-ip', // CDN 'cf-connecting-ip', // Cloudflare @@ -13,35 +15,87 @@ export const IP_ADDRESS_HEADERS = [ 'x-forwarded', ]; +/** + * Normalize IP strings to a canonical form: + * - strips IPv4-mapped IPv6 (e.g. ::ffff:192.0.2.1 -> 192.0.2.1) + * - keeps valid IPv4/IPv6 as-is (canonically formatted by ipaddr.js) + */ +function normalizeIp(ip?: string | null) { + if (!ip) return ip; + + try { + const parsed = ipaddr.parse(ip); + + if (parsed.kind() === 'ipv6' && (parsed as ipaddr.IPv6).isIPv4MappedAddress()) { + return (parsed as ipaddr.IPv6).toIPv4Address().toString(); + } + + return parsed.toString(); + } catch { + // Fallback: return original if parsing fails + return ip; + } +} + +function resolveIp(ip?: string | null) { + if (!ip) return ip; + + // First, try as-is + const normalized = normalizeIp(ip); + try { + ipaddr.parse(normalized); + return normalized; + } catch { + // try stripping port (handles IPv4:port; leaves IPv6 intact) + const stripped = stripPort(ip); + if (stripped !== ip) { + const normalizedStripped = normalizeIp(stripped); + try { + ipaddr.parse(normalizedStripped); + return normalizedStripped; + } catch { + return normalizedStripped; + } + } + + return normalized; + } +} + export function getIpAddress(headers: Headers) { const customHeader = process.env.CLIENT_IP_HEADER; if (customHeader && headers.get(customHeader)) { - return headers.get(customHeader); + return resolveIp(headers.get(customHeader)); } - const header = IP_ADDRESS_HEADERS.find(name => { - return headers.get(name); - }); + const header = IP_ADDRESS_HEADERS.find(name => headers.get(name)); + if (!header) { + return undefined; + } const ip = headers.get(header); if (header === 'x-forwarded-for') { - return ip?.split(',')?.[0]?.trim(); + return resolveIp(ip?.split(',')?.[0]?.trim()); } if (header === 'forwarded') { const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/); if (match) { - return match[1]; + return resolveIp(match[1]); } } - return ip; + return resolveIp(ip); } -export function stripPort(ip: string) { +export function stripPort(ip?: string | null) { + if (!ip) { + return ip; + } + if (ip.startsWith('[')) { const endBracket = ip.indexOf(']'); if (endBracket !== -1) {