Skip to content

Commit 88cb486

Browse files
committed
refactor: improve IP resolution logic and enhance unit tests
- Adjusted the order of headers in the IP resolution function to prioritize 'x-forwarded-for' over 'x-real-ip'. - Updated unit tests to clarify expected behavior when handling IP headers, ensuring accurate fallback logic. - Added a new test case to verify that 'x-forwarded-for' is correctly returned when present, improving test coverage.
1 parent 768266c commit 88cb486

5 files changed

Lines changed: 59 additions & 6 deletions

File tree

apps/api/src/_common/functions/resolve-client-ip.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function tcpPeerIp(req: Request): string | null {
5353
*/
5454
export function resolveClientIp(req: Request): string | null {
5555
const peerIp = tcpPeerIp(req);
56-
const orderedHeaders = ['cf-connecting-ip', 'true-client-ip', 'x-real-ip', 'x-forwarded-for'] as const;
56+
const orderedHeaders = ['cf-connecting-ip', 'true-client-ip', 'x-forwarded-for', 'x-real-ip'] as const;
5757

5858
for (const name of orderedHeaders) {
5959
const raw = headerString(req, name);

apps/api/tests/unit/_common/functions/resolve-client-ip.spec.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,26 @@ describe('resolveClientIp', () => {
2727
expect(resolveClientIp(req)).toBe('203.0.113.1');
2828
});
2929

30-
it('returns X-Real-IP before falling back to req.ip', () => {
30+
it('returns X-Real-IP when X-Forwarded-For is absent before falling back to req.ip', () => {
3131
const req = makeReq({
3232
headers: { 'x-real-ip': '192.0.2.50' },
3333
ip: '10.0.0.3',
3434
});
3535
expect(resolveClientIp(req)).toBe('192.0.2.50');
3636
});
3737

38+
it('returns X-Forwarded-For before X-Real-IP from the last proxy', () => {
39+
const req = makeReq({
40+
headers: {
41+
'x-forwarded-for': '203.0.113.10, 172.18.0.2',
42+
'x-real-ip': '172.18.0.2',
43+
},
44+
ip: '172.18.0.2',
45+
socket: { remoteAddress: '172.18.0.2' } as any,
46+
});
47+
expect(resolveClientIp(req)).toBe('203.0.113.10');
48+
});
49+
3850
it('strips IPv4-mapped IPv6 prefix', () => {
3951
const req = makeReq({
4052
headers: { 'x-real-ip': '::ffff:192.0.2.1' },

apps/web/nuxt.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,6 @@ export default defineNuxtConfig({
245245
websocket: false,
246246
},
247247
routeRules: {
248-
'/api/**': {
249-
proxy: `${SESAME_APP_API_URL}/**`,
250-
},
251248
'/api/core/backends/sse': {
252249
proxy: `${SESAME_APP_API_URL}/core/backends/sse`,
253250
// Disable compression and caching for SSE
@@ -257,6 +254,9 @@ export default defineNuxtConfig({
257254
'X-Accel-Buffering': 'no', // Disable buffering in nginx
258255
},
259256
},
257+
'/api/**': {
258+
proxy: `${SESAME_APP_API_URL}/**`,
259+
},
260260
},
261261
},
262262
experimental: {

apps/web/src/pages/identities/outdated.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template lang="pug">
22
q-page.grid.q-pa-sm
3-
q-card.col.full-height.flex.column.outdated-card(style="min-height: inherit" flat bordered)
3+
q-card.col.full-height.flex.column.outdated-card(flat bordered)
44
q-bar.bg-transparent.border-bottom
55
q-toolbar-title Identités avec invitation périmées
66
.row.items-center.no-wrap.q-px-sm.q-py-xs
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { defineEventHandler, getRequestHeader } from 'h3'
2+
3+
function normalizeRemoteAddress(value: string | undefined): string | null {
4+
if (!value) {
5+
return null
6+
}
7+
8+
const normalized = value.trim()
9+
if (!normalized) {
10+
return null
11+
}
12+
13+
return normalized.startsWith('::ffff:') ? normalized.slice(7) : normalized
14+
}
15+
16+
function appendForwardedFor(currentValue: string | undefined, remoteAddress: string): string {
17+
if (!currentValue?.trim()) {
18+
return remoteAddress
19+
}
20+
21+
return `${currentValue}, ${remoteAddress}`
22+
}
23+
24+
export default defineEventHandler((event) => {
25+
const url = event.node.req.url || ''
26+
if (!url.startsWith('/api/')) {
27+
return
28+
}
29+
30+
const remoteAddress = normalizeRemoteAddress(event.node.req.socket.remoteAddress)
31+
if (!remoteAddress) {
32+
return
33+
}
34+
35+
const currentForwardedFor = getRequestHeader(event, 'x-forwarded-for')
36+
event.node.req.headers['x-forwarded-for'] = appendForwardedFor(currentForwardedFor, remoteAddress)
37+
38+
if (!getRequestHeader(event, 'x-real-ip')) {
39+
event.node.req.headers['x-real-ip'] = currentForwardedFor?.split(',')[0]?.trim() || remoteAddress
40+
}
41+
})

0 commit comments

Comments
 (0)