Skip to content

Commit 5506314

Browse files
authored
Merge pull request #3891 from journry789/master
This resolves the issue of being unable to obtain the client's IP add…
2 parents e90b220 + 437c168 commit 5506314

File tree

1 file changed

+62
-8
lines changed

1 file changed

+62
-8
lines changed

src/lib/ip.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import ipaddr from 'ipaddr.js';
2+
13
export const IP_ADDRESS_HEADERS = [
24
'true-client-ip', // CDN
35
'cf-connecting-ip', // Cloudflare
@@ -13,35 +15,87 @@ export const IP_ADDRESS_HEADERS = [
1315
'x-forwarded',
1416
];
1517

18+
/**
19+
* Normalize IP strings to a canonical form:
20+
* - strips IPv4-mapped IPv6 (e.g. ::ffff:192.0.2.1 -> 192.0.2.1)
21+
* - keeps valid IPv4/IPv6 as-is (canonically formatted by ipaddr.js)
22+
*/
23+
function normalizeIp(ip?: string | null) {
24+
if (!ip) return ip;
25+
26+
try {
27+
const parsed = ipaddr.parse(ip);
28+
29+
if (parsed.kind() === 'ipv6' && (parsed as ipaddr.IPv6).isIPv4MappedAddress()) {
30+
return (parsed as ipaddr.IPv6).toIPv4Address().toString();
31+
}
32+
33+
return parsed.toString();
34+
} catch {
35+
// Fallback: return original if parsing fails
36+
return ip;
37+
}
38+
}
39+
40+
function resolveIp(ip?: string | null) {
41+
if (!ip) return ip;
42+
43+
// First, try as-is
44+
const normalized = normalizeIp(ip);
45+
try {
46+
ipaddr.parse(normalized);
47+
return normalized;
48+
} catch {
49+
// try stripping port (handles IPv4:port; leaves IPv6 intact)
50+
const stripped = stripPort(ip);
51+
if (stripped !== ip) {
52+
const normalizedStripped = normalizeIp(stripped);
53+
try {
54+
ipaddr.parse(normalizedStripped);
55+
return normalizedStripped;
56+
} catch {
57+
return normalizedStripped;
58+
}
59+
}
60+
61+
return normalized;
62+
}
63+
}
64+
1665
export function getIpAddress(headers: Headers) {
1766
const customHeader = process.env.CLIENT_IP_HEADER;
1867

1968
if (customHeader && headers.get(customHeader)) {
20-
return headers.get(customHeader);
69+
return resolveIp(headers.get(customHeader));
2170
}
2271

23-
const header = IP_ADDRESS_HEADERS.find(name => {
24-
return headers.get(name);
25-
});
72+
const header = IP_ADDRESS_HEADERS.find(name => headers.get(name));
73+
if (!header) {
74+
return undefined;
75+
}
2676

2777
const ip = headers.get(header);
2878

2979
if (header === 'x-forwarded-for') {
30-
return ip?.split(',')?.[0]?.trim();
80+
return resolveIp(ip?.split(',')?.[0]?.trim());
3181
}
3282

3383
if (header === 'forwarded') {
3484
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);
3585

3686
if (match) {
37-
return match[1];
87+
return resolveIp(match[1]);
3888
}
3989
}
4090

41-
return ip;
91+
return resolveIp(ip);
4292
}
4393

44-
export function stripPort(ip: string) {
94+
export function stripPort(ip?: string | null) {
95+
if (!ip) {
96+
return ip;
97+
}
98+
4599
if (ip.startsWith('[')) {
46100
const endBracket = ip.indexOf(']');
47101
if (endBracket !== -1) {

0 commit comments

Comments
 (0)