Files
BanGUI/backend/app/models/auth.py
Lukas 93021500c3 TASK-033: Remove session token from JSON response body
Fixes a critical security vulnerability where the session token was
being returned in the JSON response body of POST /api/auth/login.
This exposed the token to JavaScript, allowing malicious scripts to
steal it and bypass the HttpOnly cookie protection.

Changes:
- Backend: Remove 'token' field from LoginResponse model (auth.py)
- Backend: Update login() endpoint to return only 'expires_at'
- Frontend: Update LoginResponse type to exclude 'token' field
- Backend: Update test helper _login() to extract token from cookie
- Backend: Update test cases to verify token is NOT in response body
- Documentation: Add section 'Authentication Endpoints' in Backend-Development.md
- Documentation: Update Web-Development.md to explain HttpOnly cookie benefits

Security benefit: Session tokens are now only accessible via HttpOnly
cookies, protected from JavaScript access, XSS attacks, and malicious
third-party scripts. The frontend continues to use only the cookie for
authentication.

All auth tests pass (23 tests). Type checking and linting pass with
zero errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 19:38:33 +02:00

55 lines
1.7 KiB
Python

"""Authentication Pydantic models.
Request, response, and domain models used by the auth router and service.
"""
from pydantic import BaseModel, ConfigDict, Field
class LoginRequest(BaseModel):
"""Payload for ``POST /api/auth/login``."""
model_config = ConfigDict(strict=True)
password: str = Field(
...,
max_length=72,
description="Master password to authenticate with (max 72 bytes due to bcrypt truncation).",
)
class LoginResponse(BaseModel):
"""Successful login response.
The session token is set as an ``HttpOnly`` ``SameSite=Lax`` cookie by the
router, protecting it from JavaScript access. The JSON body contains only
the expiry timestamp, allowing the frontend to know when to prompt for
re-authentication.
For programmatic API clients that require a token in the response body,
use ``POST /api/auth/token`` instead, which does not set a cookie.
"""
model_config = ConfigDict(strict=True)
expires_at: str = Field(..., description="ISO 8601 UTC expiry timestamp.")
class LogoutResponse(BaseModel):
"""Response body for ``POST /api/auth/logout``."""
model_config = ConfigDict(strict=True)
message: str = Field(default="Logged out successfully.")
class Session(BaseModel):
"""Internal domain model representing a persisted session record."""
model_config = ConfigDict(strict=True)
id: int = Field(..., description="Auto-incremented row ID.")
token: str = Field(..., description="Opaque session token.")
created_at: str = Field(..., description="ISO 8601 UTC creation timestamp.")
expires_at: str = Field(..., description="ISO 8601 UTC expiry timestamp.")