From 20db3ecc047670ebf1ed27ad8425b6f71e49b2f3 Mon Sep 17 00:00:00 2001 From: suguanYang Date: Mon, 24 Aug 2026 09:26:08 +0800 Subject: [PATCH] fix: recognize prefixed auth session cookies Allow Notebook's proxy to recognize staging and production Better Auth token names so authenticated inspect requests are not redirected to login. Co-authored-by: Cursor --- .env.local.example | 5 +-- src/infrastructure/auth/index.test.ts | 23 ++++++++++++- src/infrastructure/auth/index.ts | 12 +++---- .../auth/session-cookie-names.ts | 27 +++++++++++---- src/proxy.test.ts | 33 +++++++++++++++++++ src/proxy.ts | 6 ++-- 6 files changed, 87 insertions(+), 19 deletions(-) diff --git a/.env.local.example b/.env.local.example index 7340c1a..1307872 100644 --- a/.env.local.example +++ b/.env.local.example @@ -39,8 +39,9 @@ DASHBOARD_ORIGIN=http://dashboard.local.knowhereto.ai:3000 # local: http://notebook.local.knowhereto.ai:3001 NOTEBOOK_PUBLIC_URL=http://notebook.local.knowhereto.ai:3001 -# Optional override of the session cookie names Dashboard sets. Defaults -# to Better Auth's standard cookie names. +# Optional additional session cookie names Dashboard sets. +# Without an override, Notebook recognizes Better Auth session-token cookies +# with standard or environment-prefixed names. # SESSION_COOKIE_NAMES=better-auth.session_token,__Secure-better-auth.session_token # --- Product analytics (client-side) --- diff --git a/src/infrastructure/auth/index.test.ts b/src/infrastructure/auth/index.test.ts index 781c80c..ad2e22b 100644 --- a/src/infrastructure/auth/index.test.ts +++ b/src/infrastructure/auth/index.test.ts @@ -24,7 +24,7 @@ vi.mock("next/cache", () => nextCacheMocks) * Playwright flow added in a later PR. */ -import { extractUser, sessionCookieNames } from "." +import { extractUser, isSessionCookieName, sessionCookieNames } from "." const SESSION_PATH = "/api/orpc/users/getCurrentUser" @@ -147,6 +147,27 @@ describe("sessionCookieNames", () => { "__Secure-better-auth.session_token", ]) }) + + it("recognizes environment-prefixed Better Auth session tokens", () => { + delete process.env.SESSION_COOKIE_NAMES + + expect( + isSessionCookieName("__Secure-better-auth-staging-session_token"), + ).toBe(true) + expect(isSessionCookieName("__Secure-better-auth-session_token")).toBe(true) + expect( + isSessionCookieName("__Secure-better-auth-staging-session_data"), + ).toBe(false) + }) + + it("adds an explicit cookie-name override without hiding Better Auth tokens", () => { + process.env.SESSION_COOKIE_NAMES = "custom-session" + + expect(isSessionCookieName("custom-session")).toBe(true) + expect( + isSessionCookieName("__Secure-better-auth-staging-session_token"), + ).toBe(true) + }) }) describe("getCurrentUser", () => { diff --git a/src/infrastructure/auth/index.ts b/src/infrastructure/auth/index.ts index b28ac70..afc2b2f 100644 --- a/src/infrastructure/auth/index.ts +++ b/src/infrastructure/auth/index.ts @@ -9,13 +9,16 @@ import { HttpClientRequest, } from "@effect/platform" import { authURLs } from "./urls" -import { sessionCookieNames } from "./session-cookie-names" +import { + isSessionCookieName, + sessionCookieNames, +} from "./session-cookie-names" import { logger } from "@/lib/logger" import { knowhereApiKeyOverride } from "@/integrations/knowhere-api-key" import { setEmptyJsonBody } from "@/integrations/dashboard/orpc-request" import { formatUnknownForLog } from "@/lib/format-log-value" -export { sessionCookieNames } +export { isSessionCookieName, sessionCookieNames } /** * Auth helpers for Knowhere Notebook. @@ -222,10 +225,7 @@ export async function hasSessionCookie(): Promise { if (knowhereApiKeyOverride.hasApiKey()) return true const jar = await cookies() - for (const name of sessionCookieNames()) { - if (jar.get(name) !== undefined) return true - } - return false + return jar.getAll().some((cookie) => isSessionCookieName(cookie.name)) } /** diff --git a/src/infrastructure/auth/session-cookie-names.ts b/src/infrastructure/auth/session-cookie-names.ts index 8b81aee..c0cbf83 100644 --- a/src/infrastructure/auth/session-cookie-names.ts +++ b/src/infrastructure/auth/session-cookie-names.ts @@ -3,13 +3,26 @@ const DEFAULT_SESSION_COOKIE_NAMES = [ "__Secure-better-auth.session_token", ] as const +const BETTER_AUTH_SESSION_COOKIE_NAME = + /^(?:__Secure-)?better-auth(?:[-.][A-Za-z0-9_]+)*[-.]session_token$/u + export function sessionCookieNames(): readonly string[] { + return configuredSessionCookieNames() ?? DEFAULT_SESSION_COOKIE_NAMES +} + +export function isSessionCookieName(name: string): boolean { + return ( + sessionCookieNames().includes(name) || + BETTER_AUTH_SESSION_COOKIE_NAME.test(name) + ) +} + +function configuredSessionCookieNames(): readonly string[] | null { const override = process.env.SESSION_COOKIE_NAMES - if (override !== undefined && override.trim().length > 0) { - return override - .split(",") - .map((s) => s.trim()) - .filter(Boolean) - } - return DEFAULT_SESSION_COOKIE_NAMES + if (override === undefined || override.trim().length === 0) return null + + return override + .split(",") + .map((name) => name.trim()) + .filter(Boolean) } diff --git a/src/proxy.test.ts b/src/proxy.test.ts index 0d80c19..d6b1f55 100644 --- a/src/proxy.test.ts +++ b/src/proxy.test.ts @@ -6,10 +6,12 @@ import { proxy } from "./proxy"; describe("proxy", () => { const originalDashboardOrigin = process.env.DASHBOARD_ORIGIN; const originalKnowhereApiKey = process.env.KNOWHERE_API_KEY; + const originalSessionCookieNames = process.env.SESSION_COOKIE_NAMES; beforeEach(() => { delete process.env.DASHBOARD_ORIGIN; delete process.env.KNOWHERE_API_KEY; + delete process.env.SESSION_COOKIE_NAMES; }); afterEach(() => { @@ -23,6 +25,11 @@ describe("proxy", () => { } else { process.env.KNOWHERE_API_KEY = originalKnowhereApiKey; } + if (originalSessionCookieNames === undefined) { + delete process.env.SESSION_COOKIE_NAMES; + } else { + process.env.SESSION_COOKIE_NAMES = originalSessionCookieNames; + } }); it("allows anonymous guest source reads", () => { @@ -84,4 +91,30 @@ describe("proxy", () => { expect(response.headers.get("x-middleware-next")).toBe("1"); }); + + it("recognizes Dashboard session tokens with an environment prefix", () => { + const response = proxy( + new NextRequest("http://localhost:3001/inspect/doc-1/chunks", { + headers: { + cookie: "__Secure-better-auth-staging-session_token=token", + }, + }), + ); + + expect(response.headers.get("x-middleware-next")).toBe("1"); + }); + + it("does not mistake Better Auth session data for a session token", () => { + const response = proxy( + new NextRequest("http://localhost:3001/inspect/doc-1/chunks", { + headers: { + cookie: "__Secure-better-auth-staging-session_data=data", + }, + }), + ); + + expect(response.headers.get("location")).toBe( + "http://localhost:3001/login", + ); + }); }); diff --git a/src/proxy.ts b/src/proxy.ts index 7527048..6c10573 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,6 +1,6 @@ import { NextResponse, type NextRequest } from "next/server" import { authURLs } from "@/infrastructure/auth/urls" -import { sessionCookieNames } from "@/infrastructure/auth/session-cookie-names" +import { isSessionCookieName } from "@/infrastructure/auth/session-cookie-names" import { knowhereApiKeyOverride } from "@/integrations/knowhere-api-key" /** @@ -59,8 +59,8 @@ export function proxy(req: NextRequest): NextResponse { if (isPublicPath(req)) return NextResponse.next() - for (const name of sessionCookieNames()) { - if (req.cookies.get(name)) return NextResponse.next() + if (req.cookies.getAll().some((cookie) => isSessionCookieName(cookie.name))) { + return NextResponse.next() } const origin = process.env.DASHBOARD_ORIGIN