import { describe, expect, it } from "vitest"; import { ACCESS_MAX_AGE_SEC, COOKIE_ACCESS, COOKIE_ID, COOKIE_OAUTH, COOKIE_REFRESH, COOKIE_SESSION_HINT, REFRESH_MAX_AGE_SEC, clearCookie, clearedSessionCookies, cookieNames, parseCookies, serializeCookie, serializeOauthCookie, sessionCookieValue, tokenCookies, } from "./cookies.js"; const host = cookieNames("dev"); const local = cookieNames("local"); describe("auth cookies", () => { it("prefixes deployed cookies with __Host- and keeps local names unprefixed", () => { expect(host).toEqual({ access: `__Host-${COOKIE_ACCESS}`, id: `__Host-${COOKIE_ID}`, refresh: `__Host-${COOKIE_REFRESH}`, oauth: `__Host-${COOKIE_OAUTH}`, hint: `__Host-${COOKIE_SESSION_HINT}`, }); expect(local).toEqual({ access: COOKIE_ACCESS, id: COOKIE_ID, refresh: COOKIE_REFRESH, oauth: COOKIE_OAUTH, hint: COOKIE_SESSION_HINT, }); }); it("sets HttpOnly, SameSite=Lax, Path=/, no Domain, and Secure outside local", () => { const cookie = serializeCookie(host.access, "tok", { maxAge: ACCESS_MAX_AGE_SEC, stage: "dev", }); expect(cookie.startsWith(`${host.access}=`)).toBe(true); expect(cookie).toContain("HttpOnly"); expect(cookie).toContain("SameSite=Lax"); expect(cookie).toContain("Path=/"); expect(cookie).toMatch(/(?:^|; )Secure(?:;|$)/); expect(cookie).not.toMatch(/Domain=/i); expect(cookie).toContain(`Max-Age=${ACCESS_MAX_AGE_SEC}`); }); it("omits Secure on local and scopes refresh to /api/auth", () => { const cookie = serializeCookie(local.access, "tok", { maxAge: ACCESS_MAX_AGE_SEC, stage: "local", }); expect(cookie).toContain("HttpOnly"); expect(cookie).not.toMatch(/(?:^|; )Secure(?:;|$)/); expect(cookie).not.toMatch(/Domain=/i); const refresh = tokenCookies( { accessToken: "a", idToken: "i", refreshToken: "r" }, "local", ).find((item) => item.startsWith(`${local.refresh}=`)); expect(refresh).toContain("Path=/api/auth"); expect(refresh).not.toMatch(/(?:^|; )Secure(?:;|$)/); }); it("sets a non-HttpOnly session hint for 8h", () => { const cookies = tokenCookies({ accessToken: "a", idToken: "i", refreshToken: "r" }, "dev"); const hint = cookies.find((item) => item.startsWith(`${host.hint}=`)); expect(hint).toContain(`${host.hint}=1`); expect(hint).toContain(`Max-Age=${REFRESH_MAX_AGE_SEC}`); expect(hint).not.toContain("HttpOnly"); expect(hint).toContain("SameSite=Lax"); expect(hint).toMatch(/(?:^|; )Secure(?:;|$)/); }); it("ignores unprefixed session cookies on deployed stages", () => { expect( sessionCookieValue({ headers: { cookie: `${COOKIE_ACCESS}=legacy` } }, "access", "dev"), ).toBeUndefined(); expect( sessionCookieValue( { headers: { cookie: `${host.access}=host; ${COOKIE_ACCESS}=legacy` } }, "access", "dev", ), ).toBe("host"); }); it("reads unprefixed session cookies only on local", () => { expect( sessionCookieValue({ headers: { cookie: `${COOKIE_ACCESS}=local` } }, "access", "local"), ).toBe("local"); }); it("parses Cookie headers and keeps the first duplicate", () => { expect( parseCookies({ headers: { cookie: `${host.access}=first; ${host.access}=second` }, }), ).toEqual({ [host.access]: "first" }); }); it("clears cookies with Max-Age=0 and the same host-only attributes", () => { const cookie = clearCookie(host.access, "dev"); expect(cookie).toContain("Max-Age=0"); expect(cookie).toContain("HttpOnly"); expect(cookie).not.toMatch(/Domain=/i); expect(cookie).toMatch(/(?:^|; )Secure(?:;|$)/); }); it("clears both __Host- and legacy ap_* cookies on deployed stages", () => { const cookies = clearedSessionCookies("dev"); expect( cookies.some((item) => item.startsWith(`${host.refresh}=`) && item.includes("Max-Age=0")), ).toBe(true); expect( cookies.some( (item) => item.startsWith(`${COOKIE_REFRESH}=`) && item.includes("Max-Age=0") && item.includes("Path=/"), ), ).toBe(true); }); it("encodes oauth state without a Domain attribute", () => { const cookie = serializeOauthCookie({ state: "st", verifier: "ver", returnTo: "/" }, "dev"); expect(cookie.startsWith(`${host.oauth}=`)).toBe(true); expect(cookie).toContain("HttpOnly"); expect(cookie).not.toMatch(/Domain=/i); }); });