From e7d8fc07e44410909bbbd3e318fbae2ac965e3f4 Mon Sep 17 00:00:00 2001 From: eleith Date: Tue, 10 Nov 2026 10:31:13 -0800 Subject: [PATCH] fix(auth): namespace Goto cookies --- .changeset/auth-cookie-prefix.md | 7 +++ apps/web/src/lib/server/auth/index.test.ts | 72 ++++++++++++++++++++++ apps/web/src/lib/server/auth/index.ts | 1 + 3 files changed, 80 insertions(+) create mode 100644 .changeset/auth-cookie-prefix.md create mode 100644 apps/web/src/lib/server/auth/index.test.ts diff --git a/.changeset/auth-cookie-prefix.md b/.changeset/auth-cookie-prefix.md new file mode 100644 index 0000000..e78c4f6 --- /dev/null +++ b/.changeset/auth-cookie-prefix.md @@ -0,0 +1,7 @@ +--- +'@goto/web': patch +'@goto/worker': patch +'@goto/cli': patch +--- + +Namespace authentication cookies with the native Better Auth `goto` prefix so Goto does not overwrite other localhost apps' default authentication cookies. Existing Goto sessions require a fresh sign-in; unrelated cookies are not removed. diff --git a/apps/web/src/lib/server/auth/index.test.ts b/apps/web/src/lib/server/auth/index.test.ts new file mode 100644 index 0000000..022a8b9 --- /dev/null +++ b/apps/web/src/lib/server/auth/index.test.ts @@ -0,0 +1,72 @@ +import { validateConfig } from '@goto/config'; +import { parseSetCookieHeader } from 'better-auth/cookies'; +import { expect, test, vi } from 'vitest'; +import { createAuthentication } from './index.js'; + +vi.mock('$app/env', () => ({ building: false })); + +const credentials = { username: 'cookie-test-admin', password: 'cookie-test-password' }; +const foreignCookies = + 'better-auth.session_token=other-app-token; better-auth.session_data=other-app-data'; + +function authentication(origin: string) { + const { configuration } = validateConfig( + { + site: { url: origin }, + auth: { secret: 'cookie-test-secret-with-at-least-thirty-two-characters', credentials }, + domains: { 'go.example.test': { links: {} } } + }, + { env: {} } + ); + return createAuthentication(configuration); +} + +function requestCookies(response: Response): string { + const cookies = parseSetCookieHeader(response.headers.get('set-cookie') ?? ''); + return [...cookies] + .filter(([, attributes]) => attributes.value) + .map(([name, attributes]) => `${name}=${attributes.value}`) + .join('; '); +} + +test.each([ + { origin: 'http://localhost:5173', prefix: 'goto.' }, + { origin: 'https://goto.example.test', prefix: '__Secure-goto.' } +])('isolates sign-in and sign-out cookies at $origin', async ({ origin, prefix }) => { + const auth = authentication(origin); + const headers = { + origin, + 'content-type': 'application/json', + 'x-real-ip': '192.0.2.44', + cookie: foreignCookies + }; + const login = await auth.handler( + new Request(`${origin}/auth/sign-in/credentials`, { + method: 'POST', + headers, + body: JSON.stringify(credentials) + }) + ); + expect(login.status).toBe(200); + const issued = parseSetCookieHeader(login.headers.get('set-cookie') ?? ''); + expect([...issued.keys()]).toContain(`${prefix}session_token`); + expect([...issued.keys()]).toContain(`${prefix}session_data`); + expect([...issued.keys()].every((name) => name.startsWith(prefix))).toBe(true); + + const cookie = `${requestCookies(login)}; ${foreignCookies}`; + const session = await auth.api.getSession({ headers: new Headers({ cookie }) }); + expect(session?.user.name).toBe(credentials.username); + + const logout = await auth.handler( + new Request(`${origin}/auth/sign-out`, { + method: 'POST', + headers: { ...headers, cookie }, + body: '{}' + }) + ); + expect(logout.status).toBe(200); + const cleared = parseSetCookieHeader(logout.headers.get('set-cookie') ?? ''); + expect([...cleared.keys()]).toContain(`${prefix}session_token`); + expect([...cleared.keys()]).toContain(`${prefix}session_data`); + expect([...cleared.keys()].every((name) => name.startsWith(prefix))).toBe(true); +}); diff --git a/apps/web/src/lib/server/auth/index.ts b/apps/web/src/lib/server/auth/index.ts index 67b3fbd..c467b07 100644 --- a/apps/web/src/lib/server/auth/index.ts +++ b/apps/web/src/lib/server/auth/index.ts @@ -34,6 +34,7 @@ export function createAuthentication(cfg: Immutable) { accountLinking: { enabled: false } }, advanced: { + cookiePrefix: 'goto', disableOriginCheck: false, ipAddress: { ipAddressHeaders: ['x-real-ip'] } }, -- 2.54.0