From fa052cf3ac1964adb84f910002038cf364a03af4 Mon Sep 17 00:00:00 2001 From: Tushar Pandey Date: Sun, 13 Jul 2025 19:36:36 +0530 Subject: [PATCH] createRequestCookies headers copy bug fix --- src/server/client.ts | 30 +++++++-- .../createRequestCookies-header-copy.test.ts | 63 +++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 src/server/createRequestCookies-header-copy.test.ts diff --git a/src/server/client.ts b/src/server/client.ts index 451007a3..266250e2 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -682,17 +682,35 @@ export class Auth0Client { } } + /** + * createRequestCookies copies the incoming request's headers to a WHATWG + * `Headers` instance while ensuring we only include *actual* header values. + * + * In Node ≥ 20 the `req.headers` object is a proxy that also enumerates + * helper methods such as `append`, `delete`, `get`, etc. Copying these + * function properties verbatim will cause the WHATWG `Headers` constructor + * to throw `TypeError: is an invalid header value`. + * + * We therefore iterate over `Object.entries(req.headers)` and copy only + * string or string[] values. This maintains backwards compatibility while + * fixing the runtime error reported in #2219. + */ private createRequestCookies(req: PagesRouterRequest) { const headers = new Headers(); - for (const key in req.headers) { - if (Array.isArray(req.headers[key])) { - for (const value of req.headers[key]) { - headers.append(key, value); + for (const [key, val] of Object.entries(req.headers)) { + if (val == null) { + continue; // skip undefined/null + } + + if (Array.isArray(val)) { + for (const v of val) { + headers.append(key, v); } - } else { - headers.append(key, req.headers[key] ?? ""); + } else if (typeof val === "string") { + headers.append(key, val); } + // ignore functions or other non-string values } return new RequestCookies(headers); diff --git a/src/server/createRequestCookies-header-copy.test.ts b/src/server/createRequestCookies-header-copy.test.ts new file mode 100644 index 00000000..ab993973 --- /dev/null +++ b/src/server/createRequestCookies-header-copy.test.ts @@ -0,0 +1,63 @@ +import type { IncomingMessage } from "node:http"; +import { describe, expect, it } from "vitest"; + +import { Auth0Client } from "./client.js"; + +/** + * Regression tests for https://github.com/auth0/nextjs-auth0/issues/2219 + * – "Headers.append: is an invalid header value". + */ + +describe("#2219 – createRequestCookies header copying", () => { + /** + * Factory that creates an Auth0Client instance with minimal (fake) config + * sufficient for creating the class without throwing configuration errors. + */ + function makeClient() { + return new Auth0Client({ + domain: "example.auth0.com", + clientId: "client_id", + clientSecret: "client_secret", + secret: "0123456789abcdef0123456789abcdef", + appBaseUrl: "http://localhost:3000" + }); + } + + /** + * Creates a bare-bones object that satisfies the subset of `IncomingMessage` + * the private `createRequestCookies()` helper expects: a `headers` record. + */ + function makePagesRouterReq(headers: Record): IncomingMessage { + return { headers } as unknown as IncomingMessage; + } + + it("ignores function properties on req.headers (no throw)", () => { + const client: any = makeClient(); + + const req = makePagesRouterReq({ + cookie: "foo=bar", + foo: "bar", + append: () => {} // bogus enumerable function property that caused the crash + }); + + expect(() => client.createRequestCookies(req)).not.toThrow(); + }); + + it("copies string and string[] values correctly", () => { + const client: any = makeClient(); + + const req = makePagesRouterReq({ + cookie: "foo=bar", + "set-cookie": ["a=1", "b=2"] + }); + + const cookies = client.createRequestCookies(req); + + // Should parse the cookie header + const foo = cookies.get("foo"); + expect(foo?.value).toBe("bar"); + + // Still exposes at least one cookie object + expect(cookies.getAll().length).toBeGreaterThanOrEqual(1); + }); +});