Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Add fallback IP in each adapter #895

Merged
merged 4 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion arcjet-bun/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import core, {
import findIP from "@arcjet/ip";
import ArcjetHeaders from "@arcjet/headers";
import type { Server } from "bun";
import { env } from "bun";

// Re-export all named exports from the generic SDK
export * from "arcjet";
Expand Down Expand Up @@ -144,12 +145,21 @@ function toArcjetRequest<Props extends PlainObject>(
const headers = new ArcjetHeaders(request.headers);

const url = new URL(request.url);
const ip = findIP(
let ip = findIP(
{
ip: ipCache.get(request),
},
headers,
);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
if (env.NODE_ENV === "development" || env.ARCJET_ENV === "development") {
// TODO: Log that the fingerprint is being overridden once the adapter
// constructs the logger
ip = "127.0.0.1";
davidmytton marked this conversation as resolved.
Show resolved Hide resolved
blaine-arcjet marked this conversation as resolved.
Show resolved Hide resolved
}
}

return {
...props,
Expand Down
14 changes: 13 additions & 1 deletion arcjet-next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,19 @@ function toArcjetRequest<Props extends PlainObject>(
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(request.headers);

const ip = findIP(request, headers);
let ip = findIP(request, headers);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
if (
process.env["NODE_ENV"] === "development" ||
process.env["ARCJET_ENV"] === "development"
) {
// TODO: Log that the fingerprint is being overridden once the adapter
// constructs the logger
ip = "127.0.0.1";
blaine-arcjet marked this conversation as resolved.
Show resolved Hide resolved
}
}
const method = request.method ?? "";
const host = headers.get("host") ?? "";
let path = "";
Expand Down
14 changes: 13 additions & 1 deletion arcjet-node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,19 @@ function toArcjetRequest<Props extends PlainObject>(
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(request.headers);

const ip = findIP(request, headers);
let ip = findIP(request, headers);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
if (
process.env["NODE_ENV"] === "development" ||
process.env["ARCJET_ENV"] === "development"
) {
// TODO: Log that the fingerprint is being overridden once the adapter
// constructs the logger
ip = "127.0.0.1";
blaine-arcjet marked this conversation as resolved.
Show resolved Hide resolved
}
}
const method = request.method ?? "";
const host = headers.get("host") ?? "";
let path = "";
Expand Down
6 changes: 6 additions & 0 deletions arcjet-sveltekit/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare module "$env/dynamic/private" {
export const env: {
NODE_ENV?: string;
ARCJET_ENV?: string;
};
}
12 changes: 11 additions & 1 deletion arcjet-sveltekit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import core, {
import findIP from "@arcjet/ip";
import ArcjetHeaders from "@arcjet/headers";
import { runtime } from "@arcjet/runtime";
import { env } from "$env/dynamic/private";

// Re-export all named exports from the generic SDK
export * from "arcjet";
Expand Down Expand Up @@ -165,12 +166,21 @@ function toArcjetRequest<Props extends PlainObject>(
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(event.request.headers);

const ip = findIP(
let ip = findIP(
{
ip: event.getClientAddress(),
},
headers,
);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
if (env.NODE_ENV === "development" || env.ARCJET_ENV === "development") {
// TODO: Log that the fingerprint is being overridden once the adapter
// constructs the logger
ip = "127.0.0.1";
blaine-arcjet marked this conversation as resolved.
Show resolved Hide resolved
}
}
const method = event.request.method;
const host = headers.get("host") ?? "";
const path = event.url.pathname;
Expand Down
2 changes: 1 addition & 1 deletion arcjet-sveltekit/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "@arcjet/tsconfig/base",
"include": ["index.ts"]
"include": ["index.ts", "env.d.ts"]
}
80 changes: 36 additions & 44 deletions ip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,24 +304,20 @@ function isGlobalIPv4(s?: unknown): s is string {
return false;
}

// TODO: Evaluate if we need to allow other IPv4 addresses in development,
// such as "This network"
if (process.env["NODE_ENV"] === "production") {
// Private IPv4 address ranges
if (octets[0] === 10) {
return false;
}
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
return false;
}
if (octets[0] === 192 && octets[1] === 168) {
return false;
}
// Private IPv4 address ranges
if (octets[0] === 10) {
return false;
}
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
return false;
}
if (octets[0] === 192 && octets[1] === 168) {
return false;
}

// Loopback address
if (octets[0] === 127) {
return false;
}
// Loopback address
if (octets[0] === 127) {
return false;
}

// Shared range
Expand Down Expand Up @@ -411,34 +407,30 @@ function isGlobalIPv6(s?: unknown): s is string {
return false;
}

// TODO: Evaluate if we need to allow other IPv6 addresses in development,
// such as "Unique local range" or "Unspecified address"
if (process.env["NODE_ENV"] === "production") {
// Loopback address
if (
segments[0] === 0 &&
segments[1] === 0 &&
segments[2] === 0 &&
segments[3] === 0 &&
segments[4] === 0 &&
segments[5] === 0 &&
segments[6] === 0 &&
segments[7] === 0x1
) {
return false;
}
// Loopback address
if (
segments[0] === 0 &&
segments[1] === 0 &&
segments[2] === 0 &&
segments[3] === 0 &&
segments[4] === 0 &&
segments[5] === 0 &&
segments[6] === 0 &&
segments[7] === 0x1
) {
return false;
}

// IPv4-mapped Address (`::ffff:0:0/96`)
if (
segments[0] === 0 &&
segments[1] === 0 &&
segments[2] === 0 &&
segments[3] === 0 &&
segments[4] === 0 &&
segments[5] === 0xffff
) {
return false;
}
// IPv4-mapped Address (`::ffff:0:0/96`)
if (
segments[0] === 0 &&
segments[1] === 0 &&
segments[2] === 0 &&
segments[3] === 0 &&
segments[4] === 0 &&
segments[5] === 0xffff
) {
return false;
}

// IPv4-IPv6 Translat. (`64:ff9b:1::/48`)
Expand Down
48 changes: 5 additions & 43 deletions ip/test/ipv4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,11 @@ function suite(make: MakeTest) {
expect(ip(request, headers)).toEqual("");
});

test("returns empty string if loopback (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("returns empty string if loopback", () => {
const [request, headers] = make("127.0.0.1");
expect(ip(request, headers)).toEqual("");
});

test("returns the loopback address (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const [request, headers] = make("127.0.0.1");
expect(ip(request, headers)).toEqual("127.0.0.1");
});

test("returns empty string if not full ip", () => {
const [request, headers] = make("12.3.4");
expect(ip(request, headers)).toEqual("");
Expand All @@ -124,42 +117,21 @@ function suite(make: MakeTest) {
expect(ip(request, headers)).toEqual("");
});

test("returns empty string if in the 10.x.x.x private range (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("returns empty string if in the 10.x.x.x private range", () => {
const [request, headers] = make("10.1.1.1");
expect(ip(request, headers)).toEqual("");
});

test("returns an address in the 10.x.x.x private range (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const [request, headers] = make("10.1.1.1");
expect(ip(request, headers)).toEqual("10.1.1.1");
});

test("returns empty string if in the 172.16.x.x-172.31.x.x private range (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("returns empty string if in the 172.16.x.x-172.31.x.x private range", () => {
const [request, headers] = make("172.18.1.1");
expect(ip(request, headers)).toEqual("");
});

test("returns an address in the 172.16.x.x-172.31.x.x private range (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const [request, headers] = make("172.18.1.1");
expect(ip(request, headers)).toEqual("172.18.1.1");
});

test("returns empty string if in the 192.168.x.x private range (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("returns empty string if in the 192.168.x.x private range", () => {
const [request, headers] = make("192.168.1.1");
expect(ip(request, headers)).toEqual("");
});

test("returns an address in the 192.168.x.x private range (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const [request, headers] = make("192.168.1.1");
expect(ip(request, headers)).toEqual("192.168.1.1");
});

test("returns empty string outside of the valid range", () => {
const [request, headers] = make("1.1.1.256");
expect(ip(request, headers)).toEqual("");
Expand Down Expand Up @@ -252,22 +224,12 @@ describe("find public IPv4", () => {
expect(ip(request, headers)).toEqual("3.3.3.3");
});

test("skips any private IP (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("skips any private IP", () => {
const request = {};
const headers = new Headers([
["X-Forwarded-For", "1.1.1.1, 2.2.2.2, 3.3.3.3, 127.0.0.1"],
]);
expect(ip(request, headers)).toEqual("3.3.3.3");
});

test("returns the loopback IP (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const request = {};
const headers = new Headers([
["X-Forwarded-For", "1.1.1.1, 2.2.2.2, 3.3.3.3, 127.0.0.1"],
]);
expect(ip(request, headers)).toEqual("127.0.0.1");
});
});
});
30 changes: 3 additions & 27 deletions ip/test/ipv6.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,16 @@ function suite(make: MakeTest) {
expect(ip(request, headers)).toEqual("");
});

test("returns empty string if loopback address (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("returns empty string if loopback address", () => {
const [request, headers] = make("::1");
expect(ip(request, headers)).toEqual("");
});

test("returns the loopback address (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const [request, headers] = make("::1");
expect(ip(request, headers)).toEqual("::1");
});

test("returns empty string if ipv4 mapped address (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("returns empty string if ipv4 mapped address", () => {
const [request, headers] = make("::ffff:127.0.0.1");
expect(ip(request, headers)).toEqual("");
});

test("returns an address if ipv4 mapped address (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const [request, headers] = make("::ffff:192.168.0.1");
expect(ip(request, headers)).toEqual("::ffff:192.168.0.1");
});

test("returns empty string if ipv4-ipv6 translat range", () => {
const [request, headers] = make("64:ff9b:1::");
expect(ip(request, headers)).toEqual("");
Expand Down Expand Up @@ -216,22 +202,12 @@ describe("find public IPv6", () => {
expect(ip(request, headers)).toEqual("abcd::");
});

test("skips any private IP (in production)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "production");
test("skips any private IP", () => {
const request = {};
const headers = new Headers([
["X-Forwarded-For", "e123::, 3.3.3.3, abcd::, ::1"],
]);
expect(ip(request, headers)).toEqual("abcd::");
});

test("returns the loopback IP (in development)", () => {
jest.replaceProperty(process.env, "NODE_ENV", "development");
const request = {};
const headers = new Headers([
["X-Forwarded-For", "abcd::, e123::, 3.3.3.3, ::1"],
]);
expect(ip(request, headers)).toEqual("::1");
});
});
});
1 change: 1 addition & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"NEXT_RUNTIME",
"VERCEL",
"VERCEL_GIT_COMMIT_SHA",
"ARCJET_ENV",
"ARCJET_BASE_URL",
"ARCJET_RUNTIME",
"ARCJET_LOG_LEVEL",
Expand Down
Loading