TASK-004: Bootstrap frontend auth state from backend session check

Validates session on app mount by calling GET /api/auth/session instead of relying
solely on cached sessionStorage. This ensures the UI state always reflects server
reality — expired or revoked sessions are detected immediately.

Changes:
- Backend: Add GET /api/auth/session endpoint (requires valid session, returns 200/401)
- Frontend: Add useSessionValidation hook for mount-time validation
- Frontend: Add SessionValidationLoading component for validation spinner
- Frontend: Update AuthProvider to call validation on mount with loading state
- Frontend: Add validateSession API function
- Docs: Update Features.md with session validation behavior
- Docs: Update Web-Development.md with session validation pattern

Handles three outcomes:
1. Valid session (200): Proceed with cached state
2. Invalid session (401): Clear sessionStorage and redirect to login
3. Network error: Don't logout (backend may be temporarily unreachable)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-26 12:00:21 +02:00
parent d982fe3efc
commit 29daaa9906
11 changed files with 1314 additions and 15 deletions

View File

@@ -12,7 +12,7 @@ from __future__ import annotations
import structlog
from fastapi import APIRouter, HTTPException, Request, Response, status
from app.dependencies import DbDep, SessionCacheDep, SessionRepoDep, SettingsDep
from app.dependencies import AuthDep, DbDep, SessionCacheDep, SessionRepoDep, SettingsDep
from app.models.auth import LoginRequest, LoginResponse, LogoutResponse
from app.services import auth_service
from app.utils.constants import SESSION_COOKIE_NAME
@@ -76,6 +76,31 @@ async def login(
return LoginResponse(token=signed_token, expires_at=expires_at)
@router.get(
"/session",
summary="Validate the current session",
)
async def validate_session(
_: AuthDep,
) -> dict[str, bool]:
"""Validate the current session.
This endpoint requires a valid session and returns 200 if the session is
valid and still active. If the session is invalid, expired, or missing,
FastAPI's ``require_auth`` dependency returns 401 automatically.
The frontend calls this on mount to bootstrap its authentication state
from the backend rather than relying solely on cached ``sessionStorage``.
Args:
_: The injected session object (unused, but its presence triggers validation).
Returns:
A simple JSON object confirming the session is valid.
"""
return {"valid": True}
@router.post(
"/logout",
response_model=LogoutResponse,