diff --git a/packages/remix/src/utils/getIpAddress.ts b/packages/remix/src/utils/getIpAddress.ts index 4fb5b9959484..a756c424f40e 100644 --- a/packages/remix/src/utils/getIpAddress.ts +++ b/packages/remix/src/utils/getIpAddress.ts @@ -52,7 +52,7 @@ export function getClientIPAddress(headers: Headers): string | null { return parseForwardedHeader(value); } - return value?.split(', '); + return value?.split(',').map((v: string) => v.trim()); }); // Flatten the array and filter out any falsy entries diff --git a/packages/remix/test/utils/getIpAddress.test.ts b/packages/remix/test/utils/getIpAddress.test.ts new file mode 100644 index 000000000000..f5cf1df07068 --- /dev/null +++ b/packages/remix/test/utils/getIpAddress.test.ts @@ -0,0 +1,42 @@ +import { getClientIPAddress } from '../../src/utils/getIpAddress'; + +class Headers { + private _headers: Record = {}; + + get(key: string): string | null { + return this._headers[key] ?? null; + } + + set(key: string, value: string): void { + this._headers[key] = value; + } +} + +describe('getClientIPAddress', () => { + it.each([ + [ + '2b01:cb19:8350:ed00:d0dd:fa5b:de31:8be5,2b01:cb19:8350:ed00:d0dd:fa5b:de31:8be5, 141.101.69.35', + '2b01:cb19:8350:ed00:d0dd:fa5b:de31:8be5', + ], + [ + '2b01:cb19:8350:ed00:d0dd:fa5b:de31:8be5, 2b01:cb19:8350:ed00:d0dd:fa5b:de31:8be5, 141.101.69.35', + '2b01:cb19:8350:ed00:d0dd:fa5b:de31:8be5', + ], + [ + '2a01:cb19:8350:ed00:d0dd:INVALID_IP_ADDR:8be5,141.101.69.35,2a01:cb19:8350:ed00:d0dd:fa5b:de31:8be5', + '141.101.69.35', + ], + [ + '2b01:cb19:8350:ed00:d0dd:fa5b:nope:8be5, 2b01:cb19:NOPE:ed00:d0dd:fa5b:de31:8be5, 141.101.69.35 ', + '141.101.69.35', + ], + ['2b01:cb19:8350:ed00:d0 dd:fa5b:de31:8be5, 141.101.69.35', '141.101.69.35'], + ])('should parse the IP from the X-Forwarded-For header %s', (headerValue, expectedIP) => { + const headers = new Headers(); + headers.set('X-Forwarded-For', headerValue); + + const ip = getClientIPAddress(headers as any); + + expect(ip).toEqual(expectedIP); + }); +});