Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ip)!: Accept Request object with Headers
Browse files Browse the repository at this point in the history
blaine-arcjet committed Oct 23, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent d886c76 commit 91360fd
Showing 10 changed files with 233 additions and 167 deletions.
2 changes: 1 addition & 1 deletion arcjet-bun/index.ts
Original file line number Diff line number Diff line change
@@ -203,8 +203,8 @@ export default function arcjet<
// workaround to the API design in Bun that requires access to the
// `Server` to lookup an IP.
ip: ipCache.get(request),
headers,
},
headers,
{ platform: platform(env) },
);
if (ip === "") {
2 changes: 1 addition & 1 deletion arcjet-deno/index.ts
Original file line number Diff line number Diff line change
@@ -205,8 +205,8 @@ export default function arcjet<
// workaround to the API design in Deno that requires access to the
// `ServeHandlerInfo` to lookup an IP.
ip: ipCache.get(request),
headers,
},
headers,
{ platform: platform(env) },
);
if (ip === "") {
9 changes: 8 additions & 1 deletion arcjet-nest/index.ts
Original file line number Diff line number Diff line change
@@ -216,7 +216,14 @@ function arcjet<
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(request.headers);

let ip = findIP(request, headers, { platform: platform(process.env) });
let ip = findIP(
{
ip: request.ip,
socket: request.socket,
headers,
},
{ platform: platform(process.env) },
);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
11 changes: 10 additions & 1 deletion arcjet-next/index.ts
Original file line number Diff line number Diff line change
@@ -255,7 +255,16 @@ export default function arcjet<
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(request.headers);

let ip = findIP(request, headers, { platform: platform(process.env) });
let ip = findIP(
{
ip: request.ip,
socket: request.socket,
info: request.info,
requestContext: request.requestContext,
headers,
},
{ platform: platform(process.env) },
);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
8 changes: 7 additions & 1 deletion arcjet-node/index.ts
Original file line number Diff line number Diff line change
@@ -205,7 +205,13 @@ export default function arcjet<
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(request.headers);

let ip = findIP(request, headers, { platform: platform(process.env) });
let ip = findIP(
{
socket: request.socket,
headers,
},
{ platform: platform(process.env) },
);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
2 changes: 1 addition & 1 deletion arcjet-remix/index.ts
Original file line number Diff line number Diff line change
@@ -180,8 +180,8 @@ export default function arcjet<
{
// The `getLoadContext` API will attach the `ip` to the context
ip: context?.ip,
headers,
},
headers,
{ platform: platform(process.env) },
);
if (ip === "") {
2 changes: 1 addition & 1 deletion arcjet-sveltekit/index.ts
Original file line number Diff line number Diff line change
@@ -194,8 +194,8 @@ export default function arcjet<
let ip = findIP(
{
ip: event.getClientAddress(),
headers,
},
headers,
{ platform: platform(process.env) },
);
if (ip === "") {
52 changes: 30 additions & 22 deletions ip/index.ts
Original file line number Diff line number Diff line change
@@ -551,6 +551,8 @@ export interface RequestLike {
info?: PartialInfo;

requestContext?: PartialRequestContext;

headers: Headers;
}

export type Platform = "cloudflare" | "fly-io" | "vercel";
@@ -580,11 +582,7 @@ export interface Options {
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
function findIP(
request: RequestLike,
headers: Headers,
options: Options = {},
): string {
function findIP(request: RequestLike, options: Options = {}): string {
// Prefer anything available via the platform over headers since headers can
// be set by users. Only if we don't have an IP available in `request` do we
// search the `headers`.
@@ -609,6 +607,16 @@ function findIP(
return requestContextIdentitySourceIP;
}

// Validate we have some object for `request.headers`
if (typeof request.headers !== "object" || request.headers === null) {
return "";
}

// Validate we have a `get()` function to use to read headers
if (typeof request.headers.get !== "function") {
return "";
}

// Platform-specific headers should only be accepted when we can determine
// that we are running on that platform. For example, the `CF-Connecting-IP`
// header should only be accepted when running on Cloudflare; otherwise, it
@@ -618,13 +626,13 @@ function findIP(

if (platform === "cloudflare") {
// CF-Connecting-IPv6: https://developers.cloudflare.com/fundamentals/reference/http-request-headers/#cf-connecting-ipv6
const cfConnectingIPv6 = headers.get("cf-connecting-ipv6");
const cfConnectingIPv6 = request.headers.get("cf-connecting-ipv6");
if (isGlobalIPv6(cfConnectingIPv6)) {
return cfConnectingIPv6;
}

// CF-Connecting-IP: https://developers.cloudflare.com/fundamentals/reference/http-request-headers/#cf-connecting-ip
const cfConnectingIP = headers.get("cf-connecting-ip");
const cfConnectingIP = request.headers.get("cf-connecting-ip");
if (isGlobalIP(cfConnectingIP)) {
return cfConnectingIP;
}
@@ -638,7 +646,7 @@ function findIP(
// Fly.io: https://fly.io/docs/machines/runtime-environment/#fly_app_name
if (platform === "fly-io") {
// Fly-Client-IP: https://fly.io/docs/networking/request-headers/#fly-client-ip
const flyClientIP = headers.get("fly-client-ip");
const flyClientIP = request.headers.get("fly-client-ip");
if (isGlobalIP(flyClientIP)) {
return flyClientIP;
}
@@ -653,7 +661,7 @@ function findIP(
// https://vercel.com/docs/edge-network/headers/request-headers#x-real-ip
// Also used by `@vercel/functions`, see:
// https://github.com/vercel/vercel/blob/d7536d52c87712b1b3f83e4b0fd535a1fb7e384c/packages/functions/src/headers.ts#L12
const xRealIP = headers.get("x-real-ip");
const xRealIP = request.headers.get("x-real-ip");
if (isGlobalIP(xRealIP)) {
return xRealIP;
}
@@ -662,7 +670,7 @@ function findIP(
// By default, it seems this will be 1 address, but they discuss trusted
// proxy forwarding so we try to parse it like normal. See
// https://vercel.com/docs/edge-network/headers/request-headers#custom-x-forwarded-for-ip
const xVercelForwardedFor = headers.get("x-vercel-forwarded-for");
const xVercelForwardedFor = request.headers.get("x-vercel-forwarded-for");
const xVercelForwardedForItems = parseXForwardedFor(xVercelForwardedFor);
// As per MDN X-Forwarded-For Headers documentation at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
@@ -679,7 +687,7 @@ function findIP(
// By default, it seems this will be 1 address, but they discuss trusted
// proxy forwarding so we try to parse it like normal. See
// https://vercel.com/docs/edge-network/headers/request-headers#custom-x-forwarded-for-ip
const xForwardedFor = headers.get("x-forwarded-for");
const xForwardedFor = request.headers.get("x-forwarded-for");
const xForwardedForItems = parseXForwardedFor(xForwardedFor);
// As per MDN X-Forwarded-For Headers documentation at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
@@ -699,13 +707,13 @@ function findIP(
}

// Standard headers used by Amazon EC2, Heroku, and others.
const xClientIP = headers.get("x-client-ip");
const xClientIP = request.headers.get("x-client-ip");
if (isGlobalIP(xClientIP)) {
return xClientIP;
}

// Load-balancers (AWS ELB) or proxies.
const xForwardedFor = headers.get("x-forwarded-for");
const xForwardedFor = request.headers.get("x-forwarded-for");
const xForwardedForItems = parseXForwardedFor(xForwardedFor);
// As per MDN X-Forwarded-For Headers documentation at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
@@ -720,56 +728,56 @@ function findIP(

// DigitalOcean.
// DO-Connecting-IP: https://www.digitalocean.com/community/questions/app-platform-client-ip
const doConnectingIP = headers.get("do-connecting-ip");
const doConnectingIP = request.headers.get("do-connecting-ip");
if (isGlobalIP(doConnectingIP)) {
return doConnectingIP;
}

// Fastly and Firebase hosting header (When forwared to cloud function)
// Fastly-Client-IP
const fastlyClientIP = headers.get("fastly-client-ip");
const fastlyClientIP = request.headers.get("fastly-client-ip");
if (isGlobalIP(fastlyClientIP)) {
return fastlyClientIP;
}

// Akamai
// True-Client-IP
const trueClientIP = headers.get("true-client-ip");
const trueClientIP = request.headers.get("true-client-ip");
if (isGlobalIP(trueClientIP)) {
return trueClientIP;
}

// Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies
// X-Real-IP
const xRealIP = headers.get("x-real-ip");
const xRealIP = request.headers.get("x-real-ip");
if (isGlobalIP(xRealIP)) {
return xRealIP;
}

// Rackspace LB and Riverbed's Stingray?
const xClusterClientIP = headers.get("x-cluster-client-ip");
const xClusterClientIP = request.headers.get("x-cluster-client-ip");
if (isGlobalIP(xClusterClientIP)) {
return xClusterClientIP;
}

const xForwarded = headers.get("x-forwarded");
const xForwarded = request.headers.get("x-forwarded");
if (isGlobalIP(xForwarded)) {
return xForwarded;
}

const forwardedFor = headers.get("forwarded-for");
const forwardedFor = request.headers.get("forwarded-for");
if (isGlobalIP(forwardedFor)) {
return forwardedFor;
}

const forwarded = headers.get("forwarded");
const forwarded = request.headers.get("forwarded");
if (isGlobalIP(forwarded)) {
return forwarded;
}

// Google Cloud App Engine
// X-Appengine-User-IP: https://cloud.google.com/appengine/docs/standard/reference/request-headers?tab=node.js
const xAppEngineUserIP = headers.get("x-appengine-user-ip");
const xAppEngineUserIP = request.headers.get("x-appengine-user-ip");
if (isGlobalIP(xAppEngineUserIP)) {
return xAppEngineUserIP;
}
Loading

0 comments on commit 91360fd

Please sign in to comment.