From 5d1f9ff72aee9a42a3b2533d3a3cee7fe758baf3 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 8 Jul 2026 15:13:42 -0500 Subject: [PATCH] fix: Fix embedded tokens not persisting across sessions (#7860) --- .../app/composables/use-token-cookie.test.ts | 81 +++++++++++++++++++ frontend/app/composables/use-token-cookie.ts | 12 ++- 2 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 frontend/app/composables/use-token-cookie.test.ts diff --git a/frontend/app/composables/use-token-cookie.test.ts b/frontend/app/composables/use-token-cookie.test.ts new file mode 100644 index 000000000..847db78b0 --- /dev/null +++ b/frontend/app/composables/use-token-cookie.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; +import { getTokenCookieOptions } from "./use-token-cookie"; + +function setLocation(protocol: string) { + Object.defineProperty(window, "location", { + value: { ...window.location, protocol }, + configurable: true, + writable: true, + }); +} + +function setFramed(framed: boolean) { + Object.defineProperty(window, "top", { + value: framed ? ({} as Window) : window, + configurable: true, + }); +} + +function stubNuxtApp(production: boolean) { + vi.stubGlobal("useNuxtApp", () => ({ + $appInfo: { production, tokenTime: 48 }, + })); +} + +describe("getTokenCookieOptions", () => { + beforeEach(() => { + setFramed(false); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + test("top-level https connection gets a lax, non-partitioned cookie", () => { + stubNuxtApp(true); + setLocation("https:"); + setFramed(false); + + const options = getTokenCookieOptions(); + + expect(options.secure).toBe(true); + expect(options.sameSite).toBe("lax"); + expect(options.partitioned).toBe(false); + }); + + test("iframe-embedded https connection gets a none, partitioned cookie", () => { + stubNuxtApp(true); + setLocation("https:"); + setFramed(true); + + const options = getTokenCookieOptions(); + + expect(options.secure).toBe(true); + expect(options.sameSite).toBe("none"); + expect(options.partitioned).toBe(true); + }); + + test("insecure (http) connection stays lax and non-partitioned even when framed", () => { + stubNuxtApp(true); + setLocation("http:"); + setFramed(true); + + const options = getTokenCookieOptions(); + + expect(options.secure).toBe(false); + expect(options.sameSite).toBe("lax"); + expect(options.partitioned).toBe(false); + }); + + test("non-production build stays lax and non-partitioned even when framed over https", () => { + stubNuxtApp(false); + setLocation("https:"); + setFramed(true); + + const options = getTokenCookieOptions(); + + expect(options.secure).toBe(false); + expect(options.sameSite).toBe("lax"); + expect(options.partitioned).toBe(false); + }); +}); diff --git a/frontend/app/composables/use-token-cookie.ts b/frontend/app/composables/use-token-cookie.ts index a2bba926e..b6acb5f79 100644 --- a/frontend/app/composables/use-token-cookie.ts +++ b/frontend/app/composables/use-token-cookie.ts @@ -1,9 +1,13 @@ export function getTokenCookieOptions() { - const isSecureConnection = useNuxtApp().$appInfo.production && window?.location?.protocol === "https:"; + const { $appInfo } = useNuxtApp(); + + const isSecureConnection = $appInfo.production && window?.location?.protocol === "https:"; + const isEmbedded = isSecureConnection && window?.self !== window?.top; + return { - maxAge: useNuxtApp().$appInfo.tokenTime * 60 * 60, + maxAge: $appInfo.tokenTime * 60 * 60, secure: isSecureConnection, - sameSite: (isSecureConnection ? "none" : "lax") as "none" | "lax", - partitioned: isSecureConnection, + sameSite: (isEmbedded ? "none" : "lax") as "none" | "lax", + partitioned: isEmbedded, }; }