This resolves the issue of being unable to obtain the client's IP address due to the IPv6 format.

This commit is contained in:
journry789 2025-12-15 13:56:39 +08:00
parent 860e6390f1
commit 437c168e6f

View file

@ -1,3 +1,5 @@
import ipaddr from 'ipaddr.js';
export const IP_ADDRESS_HEADERS = [ export const IP_ADDRESS_HEADERS = [
'true-client-ip', // CDN 'true-client-ip', // CDN
'cf-connecting-ip', // Cloudflare 'cf-connecting-ip', // Cloudflare
@ -13,35 +15,87 @@ export const IP_ADDRESS_HEADERS = [
'x-forwarded', '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) { export function getIpAddress(headers: Headers) {
const customHeader = process.env.CLIENT_IP_HEADER; const customHeader = process.env.CLIENT_IP_HEADER;
if (customHeader && headers.get(customHeader)) { if (customHeader && headers.get(customHeader)) {
return headers.get(customHeader); return resolveIp(headers.get(customHeader));
} }
const header = IP_ADDRESS_HEADERS.find(name => { const header = IP_ADDRESS_HEADERS.find(name => headers.get(name));
return headers.get(name); if (!header) {
}); return undefined;
}
const ip = headers.get(header); const ip = headers.get(header);
if (header === 'x-forwarded-for') { if (header === 'x-forwarded-for') {
return ip?.split(',')?.[0]?.trim(); return resolveIp(ip?.split(',')?.[0]?.trim());
} }
if (header === 'forwarded') { if (header === 'forwarded') {
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/); const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);
if (match) { 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('[')) { if (ip.startsWith('[')) {
const endBracket = ip.indexOf(']'); const endBracket = ip.indexOf(']');
if (endBracket !== -1) { if (endBracket !== -1) {