diff --git a/apps/api/src/auth/auth.service.spec.ts b/apps/api/src/auth/auth.service.spec.ts index bc60214..ca72b8e 100644 --- a/apps/api/src/auth/auth.service.spec.ts +++ b/apps/api/src/auth/auth.service.spec.ts @@ -426,6 +426,21 @@ describe("AuthService", () => { }); }); + it("should preserve raw cookie token value without URL re-encoding", async () => { + const auth = service.getAuth(); + const mockGetSession = vi.fn().mockResolvedValue(mockSessionData); + auth.api = { getSession: mockGetSession } as any; + + const result = await service.verifySession("tok/with+=chars="); + + expect(result).toEqual(mockSessionData); + expect(mockGetSession).toHaveBeenCalledWith({ + headers: { + cookie: "__Secure-better-auth.session_token=tok/with+=chars=", + }, + }); + }); + it("should fall back to Authorization header when cookie-based lookups miss", async () => { const auth = service.getAuth(); const mockGetSession = vi diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 6a4906d..44f5a6c 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -150,22 +150,20 @@ export class AuthService { } private buildSessionHeaderCandidates(token: string): SessionHeaderCandidate[] { - const encodedToken = encodeURIComponent(token); - return [ { headers: { - cookie: `__Secure-better-auth.session_token=${encodedToken}`, + cookie: `__Secure-better-auth.session_token=${token}`, }, }, { headers: { - cookie: `better-auth.session_token=${encodedToken}`, + cookie: `better-auth.session_token=${token}`, }, }, { headers: { - cookie: `__Host-better-auth.session_token=${encodedToken}`, + cookie: `__Host-better-auth.session_token=${token}`, }, }, { diff --git a/docs/scratchpads/362-auth-session-chain-debug.md b/docs/scratchpads/362-auth-session-chain-debug.md index a06d488..abf2168 100644 --- a/docs/scratchpads/362-auth-session-chain-debug.md +++ b/docs/scratchpads/362-auth-session-chain-debug.md @@ -112,3 +112,42 @@ Completed items: Remaining step (requires deploy): - Redeploy API with this patch and rerun live Playwright flow on `app.mosaicstack.dev` to confirm `/auth/session` returns `200` after callback. + +## Playwright Re-Check (2026-02-19, later run) + +Live flow evidence after previous deploy attempt: + +1. OAuth callback succeeds: + - `GET https://api.mosaicstack.dev/auth/oauth2/callback/authentik?code=...&state=...` -> `302` + - Redirect target observed: `https://app.mosaicstack.dev/` + - Browser cookie jar includes: + - `__Secure-better-auth.session_token` on `api.mosaicstack.dev` (HttpOnly, Secure, SameSite=Lax) + +2. Session bootstrap still fails immediately: + - `GET https://api.mosaicstack.dev/auth/session` -> `500` + - Response body shape: + - `{"success":false,"message":"An unexpected error occurred","errorId":"...","path":"/auth/session","statusCode":500}` + - Web app returns to login because session fetch fails. + +3. Frontend version mismatch observed: + - Live `POST /auth/sign-in/oauth2` response from login flow still shows callback URL pointing to `/dashboard`. + - Current repository login page uses callback URL `/`. + - This indicates deployed web image is older than current `develop` code (or stale image tag in runtime). + +## Additional Code Fix Applied Locally (pending push/deploy) + +Refined cookie candidate construction in API session verification: + +- File: `apps/api/src/auth/auth.service.ts` + - Removed URL-encoding of session token when constructing cookie headers. + - Cookie candidates now pass raw token value exactly as extracted from incoming cookie. + +Why: + +- BetterAuth cookie tokens can contain characters like `/`, `+`, and `=`. +- Re-encoding these values can mutate token bytes and cause lookup/parse failures. + +Regression test added: + +- File: `apps/api/src/auth/auth.service.spec.ts` + - `should preserve raw cookie token value without URL re-encoding`