Skip to content

Commit

Permalink
Merging 2a86901 into trunk-temp/pr-896/6008e095-c51c-4ecb-8147-a001f4…
Browse files Browse the repository at this point in the history
…e60d3c
  • Loading branch information
trunk-io[bot] authored Jun 10, 2024
2 parents 0f23cff + 2a86901 commit 4b50178
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 155 deletions.
7 changes: 7 additions & 0 deletions arcjet-bun/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ export interface ArcjetBun<Props extends PlainObject> {
) => Response | Promise<Response>;
}

function detectPlatform() {
if (typeof env["FLY_APP_NAME"] === "string" && env["FLY_APP_NAME"] !== "") {
return "fly-io" as const;
}
}

// This is provided with an `ipCache` where it attempts to lookup the IP. This
// is primarily a workaround to the API design in Bun that requires access to
// the `Server` to lookup an IP.
Expand All @@ -150,6 +156,7 @@ function toArcjetRequest<Props extends PlainObject>(
ip: ipCache.get(request),
},
headers,
{ platform: detectPlatform() },
);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
Expand Down
11 changes: 10 additions & 1 deletion arcjet-next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,23 @@ export interface ArcjetNext<Props extends PlainObject> {
): ArcjetNext<Simplify<Props & ExtraProps<Rule>>>;
}

function detectPlatform() {
if (
typeof process.env["FLY_APP_NAME"] === "string" &&
process.env["FLY_APP_NAME"] !== ""
) {
return "fly-io" as const;
}
}

function toArcjetRequest<Props extends PlainObject>(
request: ArcjetNextRequest,
props: Props,
): ArcjetRequest<Props> {
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(request.headers);

let ip = findIP(request, headers);
let ip = findIP(request, headers, { platform: detectPlatform() });
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
Expand Down
11 changes: 10 additions & 1 deletion arcjet-node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ export interface ArcjetNode<Props extends PlainObject> {
): ArcjetNode<Simplify<Props & ExtraProps<Rule>>>;
}

function detectPlatform() {
if (
typeof process.env["FLY_APP_NAME"] === "string" &&
process.env["FLY_APP_NAME"] !== ""
) {
return "fly-io" as const;
}
}

function toArcjetRequest<Props extends PlainObject>(
request: ArcjetNodeRequest,
props: Props,
Expand All @@ -143,7 +152,7 @@ function toArcjetRequest<Props extends PlainObject>(
// We construct an ArcjetHeaders to normalize over Headers
const headers = new ArcjetHeaders(request.headers);

let ip = findIP(request, headers);
let ip = findIP(request, headers, { platform: detectPlatform() });
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
// so the request doesn't fail.
Expand Down
1 change: 1 addition & 0 deletions arcjet-sveltekit/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ declare module "$env/dynamic/private" {
export const env: {
NODE_ENV?: string;
ARCJET_ENV?: string;
FLY_APP_NAME?: string;
};
}
7 changes: 7 additions & 0 deletions arcjet-sveltekit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ export interface ArcjetSvelteKit<Props extends PlainObject> {
): ArcjetSvelteKit<Simplify<Props & ExtraProps<Rule>>>;
}

function detectPlatform() {
if (typeof env["FLY_APP_NAME"] === "string" && env["FLY_APP_NAME"] !== "") {
return "fly-io" as const;
}
}

function toArcjetRequest<Props extends PlainObject>(
event: ArcjetSvelteKitRequestEvent,
props: Props,
Expand All @@ -171,6 +177,7 @@ function toArcjetRequest<Props extends PlainObject>(
ip: event.getClientAddress(),
},
headers,
{ platform: detectPlatform() },
);
if (ip === "") {
// If the `ip` is empty but we're in development mode, we default the IP
Expand Down
19 changes: 15 additions & 4 deletions ip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,12 @@ export interface RequestLike {
requestContext?: PartialRequestContext;
}

export type Platform = "cloudflare" | "fly-io";

export interface Options {
platform?: Platform;
}

// Heavily based on https://github.com/pbojinov/request-ip
//
// Licensed: The MIT License (MIT) Copyright (c) 2022 Petar Bojinov -
Expand All @@ -574,7 +580,11 @@ export interface RequestLike {
// 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): string {
function findIP(
request: RequestLike,
headers: Headers,
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`.
Expand Down Expand Up @@ -604,8 +614,9 @@ function findIP(request: RequestLike, headers: Headers): string {
// header should only be accepted when running on Cloudflare; otherwise, it
// can be spoofed.

// Cloudflare: https://developers.cloudflare.com/workers/configuration/compatibility-dates/#global-navigator
if (globalThis.navigator?.userAgent === "Cloudflare-Workers") {
const { platform } = options;

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");
if (isGlobalIPv6(cfConnectingIPv6)) {
Expand All @@ -620,7 +631,7 @@ function findIP(request: RequestLike, headers: Headers): string {
}

// Fly.io: https://fly.io/docs/machines/runtime-environment/#fly_app_name
if (process.env["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");
if (isGlobalIP(flyClientIP)) {
Expand Down
134 changes: 55 additions & 79 deletions ip/test/ipv4.test.ts
Original file line number Diff line number Diff line change
@@ -1,150 +1,125 @@
/**
* @jest-environment node
*/
import {
describe,
expect,
test,
beforeEach,
afterEach,
jest,
} from "@jest/globals";
import ip, { RequestLike } from "../index";

type MakeTest = (ip: unknown) => [RequestLike, Headers];

beforeEach(() => {
jest.replaceProperty(process, "env", {
...process.env,
FLY_APP_NAME: "testing",
});
// We inject an empty `navigator` object via jest.config.js to act like
// Cloudflare Workers
jest.replaceProperty(globalThis, "navigator", {
...globalThis.navigator,
userAgent: "Cloudflare-Workers",
});
});
import { describe, expect, test } from "@jest/globals";
import ip, { Options, RequestLike } from "../index";

afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
type MakeTest = (ip: unknown) => [RequestLike, Headers, Options | undefined];

function suite(make: MakeTest) {
test("returns empty string if unspecified", () => {
const [request, headers] = make("0.0.0.0");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("0.0.0.0");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if 'this network' address", () => {
const [request, headers] = make("0.1.2.3");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("0.1.2.3");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the shared address range", () => {
const [request, headers] = make("100.127.255.255");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("100.127.255.255");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the link local address range", () => {
const [request, headers] = make("169.254.255.255");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("169.254.255.255");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the future protocol range", () => {
const [request, headers] = make("192.0.0.1");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("192.0.0.1");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the 192.0.2.x documentation range", () => {
const [request, headers] = make("192.0.2.1");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("192.0.2.1");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the 198.51.100.x documentation range", () => {
const [request, headers] = make("198.51.100.1");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("198.51.100.1");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the 203.0.113.x documentation range", () => {
const [request, headers] = make("203.0.113.1");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("203.0.113.1");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the benchmarking range", () => {
const [request, headers] = make("198.19.255.255");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("198.19.255.255");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the reserved range", () => {
const [request, headers] = make("240.0.0.0");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("240.0.0.0");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if in the broadcast address", () => {
const [request, headers] = make("255.255.255.255");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("255.255.255.255");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if loopback", () => {
const [request, headers] = make("127.0.0.1");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("127.0.0.1");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if not full ip", () => {
const [request, headers] = make("12.3.4");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("12.3.4");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if more than 3 digits in an octet", () => {
const [request, headers] = make("1111.2.3.4");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("1111.2.3.4");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if more than full ip", () => {
const [request, headers] = make("1.2.3.4.5");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("1.2.3.4.5");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if any octet has leading 0", () => {
const [request, headers] = make("1.02.3.4");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("1.02.3.4");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string if not a string", () => {
const [request, headers] = make(["12", "3", "4"]);
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make(["12", "3", "4"]);
expect(ip(request, headers, options)).toEqual("");
});

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("");
const [request, headers, options] = make("10.1.1.1");
expect(ip(request, headers, options)).toEqual("");
});

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("");
const [request, headers, options] = make("172.18.1.1");
expect(ip(request, headers, options)).toEqual("");
});

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("");
const [request, headers, options] = make("192.168.1.1");
expect(ip(request, headers, options)).toEqual("");
});

test("returns empty string outside of the valid range", () => {
const [request, headers] = make("1.1.1.256");
expect(ip(request, headers)).toEqual("");
const [request, headers, options] = make("1.1.1.256");
expect(ip(request, headers, options)).toEqual("");
});

test("returns the ip if valid", () => {
const [request, headers] = make("1.1.1.1");
expect(ip(request, headers)).toEqual("1.1.1.1");
const [request, headers, options] = make("1.1.1.1");
expect(ip(request, headers, options)).toEqual("1.1.1.1");
});

test("returns the full ip if valid, after ignoring port", () => {
const [request, headers] = make("1.1.1.1:443");
expect(ip(request, headers)).toEqual("1.1.1.1:443");
const [request, headers, options] = make("1.1.1.1:443");
expect(ip(request, headers, options)).toEqual("1.1.1.1:443");
});
}

Expand All @@ -161,16 +136,16 @@ function requestSuite(...keys: string[]) {
}

const req = nested(keys);
return [req, new Headers()];
return [req, new Headers(), undefined];
});
});
}

function headerSuite(key: string) {
function headerSuite(key: string, options?: Options) {
describe(`header: ${key}`, () => {
suite((ip: unknown) => {
if (typeof ip === "string") {
return [{}, new Headers([[key, ip]])];
return [{}, new Headers([[key, ip]]), options];
} else {
return [
{},
Expand All @@ -181,6 +156,7 @@ function headerSuite(key: string) {
ip,
],
]),
options,
];
}
});
Expand All @@ -195,10 +171,10 @@ describe("find public IPv4", () => {

headerSuite("X-Client-IP");
headerSuite("X-Forwarded-For");
headerSuite("CF-Connecting-IP");
headerSuite("CF-Connecting-IP", { platform: "cloudflare" });
headerSuite("DO-Connecting-IP");
headerSuite("Fastly-Client-IP");
headerSuite("Fly-Client-IP");
headerSuite("Fly-Client-IP", { platform: "fly-io" });
headerSuite("True-Client-IP");
headerSuite("X-Real-IP");
headerSuite("X-Cluster-Client-IP");
Expand Down
Loading

0 comments on commit 4b50178

Please sign in to comment.