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: Support cookies and query via the protocol #214

Merged
merged 2 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
88 changes: 79 additions & 9 deletions arcjet-next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function createNextRemoteClient(
export interface ArcjetNextRequest {
headers?: Record<string, string | string[] | undefined> | Headers;

socket?: Partial<{ remoteAddress: string }>;
socket?: Partial<{ remoteAddress: string; encrypted: boolean }>;

info?: Partial<{ remoteAddress: string }>;

Expand All @@ -120,7 +120,44 @@ export interface ArcjetNextRequest {

ip?: string;

nextUrl?: Partial<{ pathname: string; search: string }>;
nextUrl?: Partial<{ pathname: string; search: string; protocol: string }>;

cookies?:
| {
[Symbol.iterator](): IterableIterator<
[string, { name: string; value: string }]
>;
}
| Partial<{ [key: string]: string }>;
}

function isIterable(val: any): val is Iterable<any> {
return typeof val?.[Symbol.iterator] === "function";
}

function cookiesToArray(
cookies?: ArcjetNextRequest["cookies"],
): { name: string; value: string }[] {
if (typeof cookies === "undefined") {
return [];
}

if (isIterable(cookies)) {
return Array.from(cookies).map(([_, cookie]) => cookie);
} else {
return Object.entries(cookies).map(([name, value]) => ({
name,
value: value ?? "",
}));
}
}

function cookiesToString(cookies?: ArcjetNextRequest["cookies"]): string {
// This is essentially the implementation of `RequestCookies#toString` in
// Next.js but normalized for NextApiRequest cookies object
return cookiesToArray(cookies)
.map((v) => `${v.name}=${encodeURIComponent(v.value)}`)
.join("; ");
}

export interface ArcjetNext<Props extends PlainObject> {
Expand Down Expand Up @@ -180,16 +217,47 @@ export default function arcjetNext<const Rules extends (Primitive | Product)[]>(
const ip = findIP(request, headers);
const method = request.method ?? "";
const host = headers.get("host") ?? "";
let path;
// TODO(#36): nextUrl has formatting logic when you `toString` but we don't account for that here
let path = "";
let query = "";
let protocol = "";
// TODO(#36): nextUrl has formatting logic when you `toString` but
// we don't account for that here
if (typeof request.nextUrl !== "undefined") {
path = request.nextUrl.pathname;
if (request.nextUrl.search !== "") {
path += "?" + request.nextUrl.search;
path = request.nextUrl.pathname ?? "";
if (typeof request.nextUrl.search !== "undefined") {
query = request.nextUrl.search;
}
if (typeof request.nextUrl.protocol !== "undefined") {
protocol = request.nextUrl.protocol;
}
} else {
path = request.url ?? "";
if (typeof request.socket?.encrypted !== "undefined") {
protocol = request.socket.encrypted ? "https:" : "http:";
} else {
protocol = "http:";
}
// Do some very simple validation, but also try/catch around URL parsing
if (
typeof request.url !== "undefined" &&
request.url !== "" &&
host !== ""
) {
try {
const url = new URL(request.url, `${protocol}//${host}`);
path = url.pathname;
query = url.search;
protocol = url.protocol;
} catch {
// If the parsing above fails, just set the path as whatever url we
// received.
// TODO: Maybe add some local logging
blaine-arcjet marked this conversation as resolved.
Show resolved Hide resolved
path = request.url ?? "";
}
} else {
path = request.url ?? "";
}
}
const cookies = cookiesToString(request.cookies);

const extra: { [key: string]: string } = {};

Expand All @@ -213,10 +281,12 @@ export default function arcjetNext<const Rules extends (Primitive | Product)[]>(
...props,
ip,
method,
protocol: "",
protocol,
host,
path,
headers,
cookies,
query,
...extra,
// TODO(#220): The generic manipulations get really mad here, so we just cast it
} as ArcjetRequest<ExtraProps<Rules>>);
Expand Down
2 changes: 2 additions & 0 deletions arcjet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ export function createRemoteClient(
host: details.host,
path: details.path,
headers: Object.fromEntries(details.headers.entries()),
cookies: details.cookies,
query: details.query,
// TODO(#208): Re-add body
// body: details.body,
extra: extraProps(details),
Expand Down
40 changes: 32 additions & 8 deletions arcjet/test/index.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,8 @@ describe("Primitive > detectBot", () => {
host: "example.com",
path: "/",
headers: new Headers(),
cookies: "",
query: "",
extra: {},
};

Expand Down Expand Up @@ -1658,6 +1660,8 @@ describe("Primitive > detectBot", () => {
"Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15",
],
]),
cookies: "",
query: "",
"extra-test": "extra-test-value",
};

Expand Down Expand Up @@ -1708,6 +1712,8 @@ describe("Primitive > detectBot", () => {
"Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15",
],
]),
cookies: "",
query: "",
"extra-test": "extra-test-value",
};

Expand Down Expand Up @@ -1758,6 +1764,8 @@ describe("Primitive > detectBot", () => {
"Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15",
],
]),
cookies: "",
query: "",
"extra-test": "extra-test-value",
};

Expand Down Expand Up @@ -1795,6 +1803,8 @@ describe("Primitive > detectBot", () => {
"Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15",
],
]),
cookies: "",
query: "",
"extra-test": "extra-test-value",
};

Expand Down Expand Up @@ -1849,6 +1859,8 @@ describe("Primitive > detectBot", () => {
host: "example.com",
path: "/",
headers: new Headers([["User-Agent", "curl/8.1.2"]]),
cookies: "",
query: "",
"extra-test": "extra-test-value",
};

Expand Down Expand Up @@ -1898,6 +1910,8 @@ describe("Primitive > detectBot", () => {
"Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15",
],
]),
cookies: "",
query: "",
"extra-test": "extra-test-value",
};

Expand Down Expand Up @@ -1936,6 +1950,8 @@ describe("Primitive > detectBot", () => {
host: "example.com",
path: "/",
headers: new Headers([["User-Agent", "curl/8.1.2"]]),
cookies: "",
query: "",
"extra-test": "extra-test-value",
};

Expand Down Expand Up @@ -2783,7 +2799,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "foobarbaz@example.com",
};

Expand Down Expand Up @@ -2813,7 +2830,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "foobarbaz",
};

Expand Down Expand Up @@ -2843,7 +2861,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "foobarbaz@localhost",
};

Expand Down Expand Up @@ -2873,7 +2892,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "foobarbaz@localhost",
};

Expand Down Expand Up @@ -2905,7 +2925,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "@example.com",
};

Expand Down Expand Up @@ -2935,7 +2956,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "foobarbaz@[127.0.0.1]",
};

Expand Down Expand Up @@ -2965,7 +2987,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "foobarbaz@localhost",
};

Expand Down Expand Up @@ -2997,7 +3020,8 @@ describe("Primitive > validateEmail", () => {
host: "example.com",
path: "/",
headers: new Headers(),
extra: {},
cookies: "",
query: "",
email: "foobarbaz@[127.0.0.1]",
};

Expand Down
2 changes: 2 additions & 0 deletions protocol/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ export interface ArcjetRequestDetails {
path: string;
// TODO(#215): Allow `Record<string, string>` and `Record<string, string[]>`?
headers: Headers;
cookies: string;
query: string;
}

export type ArcjetRule<Props extends {} = {}> = {
Expand Down
Loading