Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/hot-chicken-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": patch
---

fix cookie decoding for Node and Cloudflare
1 change: 1 addition & 0 deletions packages/open-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@tsconfig/node18": "^1.0.1",
"aws4fetch": "^1.0.18",
"chalk": "^5.3.0",
"cookie": "^1.0.2",
"esbuild": "catalog:",
"express": "5.0.1",
"path-to-regexp": "^6.3.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/open-next/src/http/openNextResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Transform } from "node:stream";

import type { StreamCreator } from "types/open-next";
import { debug } from "../adapters/logger";
import { parseCookies, parseHeaders } from "./util";
import { parseHeaders, parseSetCookieHeader } from "./util";

const SET_COOKIE_HEADER = "set-cookie";
const CANNOT_BE_USED = "This cannot be used in OpenNext";
Expand Down Expand Up @@ -152,7 +152,7 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse {
...this.initialHeaders,
...this.headers,
};
const initialCookies = parseCookies(
const initialCookies = parseSetCookieHeader(
this.initialHeaders[SET_COOKIE_HEADER]?.toString(),
);
this._cookies =
Expand Down
8 changes: 7 additions & 1 deletion packages/open-next/src/http/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ export const convertHeader = (header: http.OutgoingHttpHeader) => {
return String(header);
};

export function parseCookies(
/**
* Parses a (coma-separated) list of Set-Cookie headers
*
* @param cookies A coma-separated list of Set-Cookie headers or a list of Set-Cookie header
* @returns A list of Set-Cookie header
*/
export function parseSetCookieHeader(
cookies: string | string[] | null | undefined,
): string[] {
if (!cookies) {
Expand Down
4 changes: 2 additions & 2 deletions packages/open-next/src/overrides/converters/aws-apigw-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
APIGatewayProxyEventV2,
APIGatewayProxyResultV2,
} from "aws-lambda";
import { parseCookies } from "http/util";
import { parseSetCookieHeader } from "http/util";
import type { InternalEvent, InternalResult } from "types/open-next";
import type { Converter } from "types/overrides";
import { fromReadableStream } from "utils/stream";
Expand Down Expand Up @@ -122,7 +122,7 @@ async function convertToApiGatewayProxyResultV2(
const response: APIGatewayProxyResultV2 = {
statusCode: result.statusCode,
headers,
cookies: parseCookies(result.headers["set-cookie"]),
cookies: parseSetCookieHeader(result.headers["set-cookie"]),
body,
isBase64Encoded: result.isBase64Encoded,
};
Expand Down
12 changes: 7 additions & 5 deletions packages/open-next/src/overrides/converters/aws-cloudfront.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
CloudFrontRequestEvent,
CloudFrontRequestResult,
} from "aws-lambda";
import { parseCookies } from "http/util";
import { parseSetCookieHeader } from "http/util";
import type {
InternalEvent,
InternalResult,
Expand Down Expand Up @@ -133,10 +133,12 @@ function convertToCloudfrontHeaders(
)
.forEach(([key, value]) => {
if (key === "set-cookie") {
cloudfrontHeaders[key] = parseCookies(`${value}`).map((cookie) => ({
key,
value: cookie,
}));
cloudfrontHeaders[key] = parseSetCookieHeader(`${value}`).map(
(cookie) => ({
key,
value: cookie,
}),
);
return;
}

Expand Down
15 changes: 8 additions & 7 deletions packages/open-next/src/overrides/converters/edge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Buffer } from "node:buffer";

import { parseCookies } from "http/util";
import cookieParser from "cookie";
import { parseSetCookieHeader } from "http/util";
import type {
InternalEvent,
InternalResult,
Expand Down Expand Up @@ -29,11 +30,11 @@ const converter: Converter<InternalEvent, InternalResult | MiddlewareResult> = {
const rawPath = url.pathname;
const method = event.method;
const shouldHaveBody = method !== "GET" && method !== "HEAD";
const cookies: Record<string, string> = Object.fromEntries(
parseCookies(event.headers.get("cookie")).map((cookie) =>
cookie.split("="),
),
);

const cookieHeader = event.headers.get("cookie");
const cookies = cookieHeader
? (cookieParser.parse(cookieHeader) as Record<string, string>)
: {};

return {
type: "core",
Expand Down Expand Up @@ -81,7 +82,7 @@ const converter: Converter<InternalEvent, InternalResult | MiddlewareResult> = {
if (key === "set-cookie" && typeof value === "string") {
// If the value is a string, we need to parse it into an array
// This is the case for middleware direct result
const cookies = parseCookies(value);
const cookies = parseSetCookieHeader(value);
for (const cookie of cookies) {
headers.append(key, cookie);
}
Expand Down
15 changes: 8 additions & 7 deletions packages/open-next/src/overrides/converters/node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IncomingMessage } from "node:http";

import { parseCookies } from "http/util";
import cookieParser from "cookie";
import type { InternalResult } from "types/open-next";
import type { Converter } from "types/overrides";
import { extractHostFromHeaders, getQueryFromSearchParams } from "./utils.js";
Expand Down Expand Up @@ -30,6 +30,12 @@ const converter: Converter = {
`${req.protocol ? req.protocol : "http"}://${extractHostFromHeaders(headers)}${req.url}`,
);
const query = getQueryFromSearchParams(url.searchParams);

const cookieHeader = req.headers.cookie;
const cookies = cookieHeader
? (cookieParser.parse(cookieHeader) as Record<string, string>)
: {};

return {
type: "core",
method: req.method ?? "GET",
Expand All @@ -42,12 +48,7 @@ const converter: Converter = {
req.socket.remoteAddress ??
"::1",
query,
cookies: Object.fromEntries(
parseCookies(req.headers.cookie)?.map((cookie) => {
const [key, value] = cookie.split("=");
return [key, value];
}) ?? [],
),
cookies,
};
},
// Nothing to do here, it's streaming
Expand Down
4 changes: 2 additions & 2 deletions packages/tests-unit/tests/converters/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ describe("convertFrom", () => {
headers: {
"content-length": "2",
"content-type": "application/json",
cookie: ["foo=bar", "hello=world"],
cookie: "foo=bar; hello=world",
},
remoteAddress: "::1",
body: Buffer.from("{}"),
Expand All @@ -201,7 +201,7 @@ describe("convertFrom", () => {
headers: {
"content-length": "2",
"content-type": "application/json",
cookie: "foo=bar,hello=world",
cookie: "foo=bar; hello=world",
},
remoteAddress: "::1",
body: Buffer.from("{}"),
Expand Down
20 changes: 10 additions & 10 deletions packages/tests-unit/tests/http/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { parseCookies } from "@opennextjs/aws/http/util.js";
import { parseSetCookieHeader } from "@opennextjs/aws/http/util.js";

describe("parseCookies", () => {
describe("parseSetCookieHeader", () => {
it("returns an empty list if cookies is emptyish", () => {
expect(parseCookies("")).toEqual([]);
expect(parseCookies(null)).toEqual([]);
expect(parseCookies(undefined)).toEqual([]);
expect(parseCookies([])).toEqual([]);
expect(parseSetCookieHeader("")).toEqual([]);
expect(parseSetCookieHeader(null)).toEqual([]);
expect(parseSetCookieHeader(undefined)).toEqual([]);
expect(parseSetCookieHeader([])).toEqual([]);
});
it("parse single cookie", () => {
const cookies = parseCookies(
const cookies = parseSetCookieHeader(
"cookie1=value1; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure; Path=/",
);
expect(cookies).toEqual([
Expand All @@ -17,7 +17,7 @@ describe("parseCookies", () => {
});
it("parse multiple cookies", () => {
// NOTE: expires is lower case but still works
const cookies = parseCookies(
const cookies = parseSetCookieHeader(
"cookie1=value1; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure; Path=/, cookie2=value2; HttpOnly; Secure",
);
expect(cookies).toEqual([
Expand All @@ -26,15 +26,15 @@ describe("parseCookies", () => {
]);
});
it("return if cookies is already an array", () => {
const cookies = parseCookies([
const cookies = parseSetCookieHeader([
"cookie1=value1; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure; Path=/",
]);
expect(cookies).toEqual([
"cookie1=value1; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure; Path=/",
]);
});
it("parses w/o Expire", () => {
const cookies = parseCookies(
const cookies = parseSetCookieHeader(
"cookie1=value1; HttpOnly; Secure; Path=/, cookie2=value2; HttpOnly=false; Secure=True; Domain=example.com; Path=/api",
);
expect(cookies).toEqual([
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.