import isIP from "is-ip"; /** * This is the list of headers, in order of preference, that will be used to * determine the client's IP address. */ const headerNames = Object.freeze([ "X-Client-IP", "CF-Connecting-IP", "X-Forwarded-For", "Fly-Client-IP", "Fastly-Client-Ip", "True-Client-Ip", "X-Real-IP", "X-Cluster-Client-IP", "X-Forwarded", "Forwarded-For", "Forwarded", "DO-Connecting-IP" /** Digital ocean app platform */, ] as const); /** * Get the IP address of the client sending a request. * * It receives the Request object or the 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 * - DO-Connecting-IP * * 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; export function getClientIPAddress(request: Request): string | null; export function getClientIPAddress(requestOrHeaders: Request | Headers): string | null { const headers = requestOrHeaders instanceof Request ? requestOrHeaders.headers : requestOrHeaders; let ipAddress = headerNames .flatMap((headerName) => { let value = headers.get(headerName); if (headerName === "Forwarded") { return parseForwardedHeader(value); } if (!value?.includes(",")) return value; return value.split(",").map((ip) => ip.trim()); }) .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 (let part of value.split(";")) { if (part.startsWith("for=")) return part.slice(4); continue; } return null; }