From e5aa3c630b1849b6eeb49f50eac7a1bc67d56171 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Tue, 22 Nov 2022 18:29:21 +0000 Subject: [PATCH 1/3] fix(remix): Attempt to extract user IP from request headers. --- packages/remix/src/utils/getIpAddress.ts | 79 ++++++++++++++++++++++++ packages/remix/src/utils/web-fetch.ts | 13 ++++ 2 files changed, 92 insertions(+) create mode 100644 packages/remix/src/utils/getIpAddress.ts diff --git a/packages/remix/src/utils/getIpAddress.ts b/packages/remix/src/utils/getIpAddress.ts new file mode 100644 index 000000000000..a4aec80d4e6b --- /dev/null +++ b/packages/remix/src/utils/getIpAddress.ts @@ -0,0 +1,79 @@ +// Vendored / modified from @sergiodxa/remix-utils +// https://github.com/sergiodxa/remix-utils/blob/02af80e12829a53696bfa8f3c2363975cf59f55e/src/server/get-client-ip-address.ts + +import { isIP } from 'net'; + +/** + * This is the list of headers, in order of preference, that will be used to + * determine the client's IP address. + */ +const headerNames = [ + 'X-Client-IP', + 'X-Forwarded-For', + 'Fly-Client-IP', + 'CF-Connecting-IP', + 'Fastly-Client-Ip', + 'True-Client-Ip', + 'X-Real-IP', + 'X-Cluster-Client-IP', + 'X-Forwarded', + 'Forwarded-For', + 'Forwarded', +]; +/** + * Get the IP address of the client sending a request. + * + * It receives a Request headers object and use it to get the + * IP address from one of the following headers in order. + * + * - X-Client-IP + * - X-Forwarded-For + * - Fly-Client-IP + * - CF-Connecting-IP + * - Fastly-Client-Ip + * - True-Client-Ip + * - X-Real-IP + * - X-Cluster-Client-IP + * - X-Forwarded + * - Forwarded-For + * - Forwarded + * + * If the IP address is valid, it will be returned. Otherwise, null will be + * returned. + * + * If the header values contains more than one IP address, the first valid one + * will be returned. + */ +export function getClientIPAddress(headers: Headers): string | null { + const ipAddress = headerNames + .map((headerName: string) => { + const value = headers.get(headerName); + if (headerName === 'Forwarded') { + return parseForwardedHeader(value); + } + if (!value?.includes(', ')) return value; + return value.split(', '); + }) + .reduce((acc: string[], val) => { + if (!val) { + return acc; + } + + return acc.concat(val); + }, []) + .find(ip => { + if (ip === null) return false; + return isIP(ip); + }); + + return ipAddress ?? null; +} + +function parseForwardedHeader(value: string | null): string | null { + if (!value) return null; + for (const part of value.split(';')) { + if (part.startsWith('for=')) return part.slice(4); + continue; + } + return null; +} diff --git a/packages/remix/src/utils/web-fetch.ts b/packages/remix/src/utils/web-fetch.ts index 599f914a94ef..1a3ef151f2e6 100644 --- a/packages/remix/src/utils/web-fetch.ts +++ b/packages/remix/src/utils/web-fetch.ts @@ -1,6 +1,7 @@ // Based on Remix's implementation of Fetch API // https://github.com/remix-run/web-std-io/tree/main/packages/fetch +import { getClientIPAddress } from './getIpAddress'; import { RemixRequest } from './types'; /* @@ -92,6 +93,15 @@ export const normalizeRemixRequest = (request: RemixRequest): Record Date: Thu, 24 Nov 2022 10:16:22 +0000 Subject: [PATCH 2/3] Update packages/remix/src/utils/getIpAddress.ts Co-authored-by: Katie Byers --- packages/remix/src/utils/getIpAddress.ts | 88 +++++++++++++----------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/packages/remix/src/utils/getIpAddress.ts b/packages/remix/src/utils/getIpAddress.ts index a4aec80d4e6b..c0396c9f0ee1 100644 --- a/packages/remix/src/utils/getIpAddress.ts +++ b/packages/remix/src/utils/getIpAddress.ts @@ -3,23 +3,6 @@ import { isIP } from 'net'; -/** - * This is the list of headers, in order of preference, that will be used to - * determine the client's IP address. - */ -const headerNames = [ - 'X-Client-IP', - 'X-Forwarded-For', - 'Fly-Client-IP', - 'CF-Connecting-IP', - 'Fastly-Client-Ip', - 'True-Client-Ip', - 'X-Real-IP', - 'X-Cluster-Client-IP', - 'X-Forwarded', - 'Forwarded-For', - 'Forwarded', -]; /** * Get the IP address of the client sending a request. * @@ -45,35 +28,58 @@ const headerNames = [ * will be returned. */ export function getClientIPAddress(headers: Headers): string | null { - const ipAddress = headerNames - .map((headerName: string) => { - const value = headers.get(headerName); - if (headerName === 'Forwarded') { - return parseForwardedHeader(value); - } - if (!value?.includes(', ')) return value; - return value.split(', '); - }) - .reduce((acc: string[], val) => { - if (!val) { - return acc; - } + // The headers to check, in priority order + const headerNames = [ + "X-Client-IP", + "X-Forwarded-For", + "Fly-Client-IP", + "CF-Connecting-IP", + "Fastly-Client-Ip", + "True-Client-Ip", + "X-Real-IP", + "X-Cluster-Client-IP", + "X-Forwarded", + "Forwarded-For", + "Forwarded", + ]; + + // This will end up being Array because of the various possible values a header + // can take + const headerValues = headerNames.map((headerName: string) => { + const value = headers.get(headerName); + + if (headerName === "Forwarded") { + return parseForwardedHeader(value); + } + + return value?.split(", "); + }); + + // Flatten the array and filter out any falsy entries + const flattenedHeaderValues = headerValues.reduce((acc: string[], val) => { + if (!val) { + return acc; + } - return acc.concat(val); - }, []) - .find(ip => { - if (ip === null) return false; - return isIP(ip); - }); + return acc.concat(val); + }, []); - return ipAddress ?? null; + // Find the first value which is a valid IP address, if any + const ipAddress = flattenedHeaderValues.find((ip) => ip !== null && isIP(ip)); + + return ipAddress || null; } function parseForwardedHeader(value: string | null): string | null { - if (!value) return null; - for (const part of value.split(';')) { - if (part.startsWith('for=')) return part.slice(4); - continue; + if (!value) { + return null; + } + + for (const part of value.split(";")) { + if (part.startsWith("for=")) { + return part.slice(4); + } } + return null; } From 750267710e732ffe6a5cb8868ca9af6fb8dc0237 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Thu, 24 Nov 2022 12:16:09 +0000 Subject: [PATCH 3/3] Fix linter. --- packages/remix/src/utils/getIpAddress.ts | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/remix/src/utils/getIpAddress.ts b/packages/remix/src/utils/getIpAddress.ts index c0396c9f0ee1..4fb5b9959484 100644 --- a/packages/remix/src/utils/getIpAddress.ts +++ b/packages/remix/src/utils/getIpAddress.ts @@ -30,29 +30,29 @@ import { isIP } from 'net'; export function getClientIPAddress(headers: Headers): string | null { // The headers to check, in priority order const headerNames = [ - "X-Client-IP", - "X-Forwarded-For", - "Fly-Client-IP", - "CF-Connecting-IP", - "Fastly-Client-Ip", - "True-Client-Ip", - "X-Real-IP", - "X-Cluster-Client-IP", - "X-Forwarded", - "Forwarded-For", - "Forwarded", + 'X-Client-IP', + 'X-Forwarded-For', + 'Fly-Client-IP', + 'CF-Connecting-IP', + 'Fastly-Client-Ip', + 'True-Client-Ip', + 'X-Real-IP', + 'X-Cluster-Client-IP', + 'X-Forwarded', + 'Forwarded-For', + 'Forwarded', ]; // This will end up being Array because of the various possible values a header // can take const headerValues = headerNames.map((headerName: string) => { const value = headers.get(headerName); - - if (headerName === "Forwarded") { + + if (headerName === 'Forwarded') { return parseForwardedHeader(value); } - - return value?.split(", "); + + return value?.split(', '); }); // Flatten the array and filter out any falsy entries @@ -65,7 +65,7 @@ export function getClientIPAddress(headers: Headers): string | null { }, []); // Find the first value which is a valid IP address, if any - const ipAddress = flattenedHeaderValues.find((ip) => ip !== null && isIP(ip)); + const ipAddress = flattenedHeaderValues.find(ip => ip !== null && isIP(ip)); return ipAddress || null; } @@ -75,8 +75,8 @@ function parseForwardedHeader(value: string | null): string | null { return null; } - for (const part of value.split(";")) { - if (part.startsWith("for=")) { + for (const part of value.split(';')) { + if (part.startsWith('for=')) { return part.slice(4); } }