Skip to content

Commit 6e3355d

Browse files
authored
fix(auth): POST /auth/login now sets cq_session cookie (#251)
Fixes critical FO-1d regression — /auth/login now sets the cookie the React app needs.
1 parent 7d76a08 commit 6e3355d

1 file changed

Lines changed: 22 additions & 10 deletions

File tree

  • server/backend/src/cq_server

server/backend/src/cq_server/auth.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import bcrypt
2525
import jwt
26-
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request
26+
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, Response
2727
from pydantic import BaseModel, Field
2828

2929
from . import aigrp
@@ -405,32 +405,44 @@ async def require_admin(
405405

406406

407407
@router.post("/login")
408-
async def login(request: LoginRequest, store: SqliteStore = Depends(get_store)) -> LoginResponse:
409-
"""Authenticate a user and return a JWT token.
408+
async def login(
409+
request: LoginRequest,
410+
response: Response,
411+
store: SqliteStore = Depends(get_store),
412+
) -> LoginResponse:
413+
"""Authenticate a user, set the cq_session cookie, and return a JWT.
414+
415+
Post-FO-1d the React admin app reads its session from the
416+
HttpOnly cookie rather than localStorage (#199). The JWT in the
417+
response body is kept for non-browser callers (curl scripts, the
418+
8l-cli) that send ``Authorization: Bearer`` instead.
410419
411420
Args:
412421
request: Login credentials.
422+
response: FastAPI response — used to set the session cookie.
413423
store: The store dependency.
414424
415425
Returns:
416-
A LoginResponse with a signed JWT and the username.
426+
A LoginResponse with a signed JWT + username. Browsers don't
427+
need to read the JWT; the cookie carries the session.
417428
418429
Raises:
419430
HTTPException: With status 401 if credentials are invalid.
420431
"""
432+
from .web_session import mint_session_cookie
433+
421434
user = await store.get_user(request.username)
422435
if user is None or not verify_password(request.password, user["password_hash"]):
423436
raise HTTPException(status_code=401, detail="Invalid username or password")
424437
# H-1: refuse session minting when the user's persona is soft-disabled.
425438
assignment = await store.get_persona_assignment(request.username)
426439
if assignment is not None and assignment.get("disabled_at") is not None:
427440
raise HTTPException(status_code=403, detail="user is disabled")
428-
token = create_token(
429-
request.username,
430-
secret=_get_jwt_secret(),
431-
aud=SESSION_AUDIENCE,
432-
)
433-
return LoginResponse(token=token, username=request.username)
441+
# Set the HttpOnly + SameSite=Lax cq_session cookie. Without this
442+
# the post-#199 React app has no way to authenticate subsequent
443+
# requests — localStorage token storage was removed by FO-1d.
444+
session_token = mint_session_cookie(response, username=request.username)
445+
return LoginResponse(token=session_token, username=request.username)
434446

435447

436448
@dataclass

0 commit comments

Comments
 (0)