From 3745f5be69e126f5453e9fa7fd12a48b6db85985 Mon Sep 17 00:00:00 2001 From: Barcode Betty Date: Sat, 4 Apr 2026 20:32:43 +0000 Subject: [PATCH] fix(auth): parse compound Better-Auth cookie/bearer token to extract token part Better-Auth sets the session cookie as "token.sessionId". The DB stores only the token part, so passing the full compound value caused 401s. Splits on "." for both cookie and Bearer paths. Tests added for compound cookie, raw token cookie (regression), and compound Bearer token. Co-Authored-By: Paperclip --- api/src/cartsnitch_api/auth/dependencies.py | 7 ++- api/tests/test_auth/test_auth_endpoints.py | 50 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/api/src/cartsnitch_api/auth/dependencies.py b/api/src/cartsnitch_api/auth/dependencies.py index 390b40e..5040741 100644 --- a/api/src/cartsnitch_api/auth/dependencies.py +++ b/api/src/cartsnitch_api/auth/dependencies.py @@ -71,11 +71,14 @@ async def get_current_user( # 1. Check session cookie — prefer __Secure- variant (HTTPS) over plain (HTTP dev) cookie_token = request.cookies.get(SECURE_SESSION_COOKIE_NAME) or request.cookies.get(SESSION_COOKIE_NAME) if cookie_token: - token = cookie_token + # Better-Auth cookie format is "token.sessionId" — extract just the token part + token = cookie_token.split(".")[0] if "." in cookie_token else cookie_token # 2. Fall back to Bearer header if not token and credentials: - token = credentials.credentials + # Callers might pass the compound value here too + raw = credentials.credentials + token = raw.split(".")[0] if "." in raw else raw if not token: raise HTTPException( diff --git a/api/tests/test_auth/test_auth_endpoints.py b/api/tests/test_auth/test_auth_endpoints.py index 7b096ae..9b55a4c 100644 --- a/api/tests/test_auth/test_auth_endpoints.py +++ b/api/tests/test_auth/test_auth_endpoints.py @@ -71,6 +71,56 @@ async def test_delete_me(client, auth_headers): assert resp.status_code == 404 +@pytest.mark.asyncio +async def test_get_me_compound_cookie(client, db_engine): + """Compound cookie value (token.sessionId) must be parsed to extract the token part.""" + from tests.conftest import _create_test_user_and_session + + _, session_token = await _create_test_user_and_session( + client, db_engine, email="compound@example.com", display_name="Compound User" + ) + compound = f"{session_token}.B0atkJCFxK1rZlwWPMK97nVO2LnyDun7" + resp = await client.get( + "/auth/me", + headers={"Cookie": f"better-auth.session_token={compound}"}, + ) + assert resp.status_code == 200 + assert resp.json()["email"] == "compound@example.com" + + +@pytest.mark.asyncio +async def test_get_me_raw_token_cookie(client, db_engine): + """Raw token (no dot) in cookie must still work — regression guard.""" + from tests.conftest import _create_test_user_and_session + + _, session_token = await _create_test_user_and_session( + client, db_engine, email="rawcookie@example.com", display_name="Raw Cookie User" + ) + resp = await client.get( + "/auth/me", + headers={"Cookie": f"better-auth.session_token={session_token}"}, + ) + assert resp.status_code == 200 + assert resp.json()["email"] == "rawcookie@example.com" + + +@pytest.mark.asyncio +async def test_get_me_compound_bearer(client, db_engine): + """Compound Bearer token (token.sessionId) must be parsed to extract the token part.""" + from tests.conftest import _create_test_user_and_session + + _, session_token = await _create_test_user_and_session( + client, db_engine, email="compoundbearer@example.com", display_name="Compound Bearer User" + ) + compound = f"{session_token}.B0atkJCFxK1rZlwWPMK97nVO2LnyDun7" + resp = await client.get( + "/auth/me", + headers={"Authorization": f"Bearer {compound}"}, + ) + assert resp.status_code == 200 + assert resp.json()["email"] == "compoundbearer@example.com" + + @pytest.mark.asyncio async def test_expired_session_rejected(client, db_engine): """Expired sessions must be rejected."""