fix(auth): 401 dal backend svuota la sessione e segnala session_expired (#25)

api_client._handle_response: su 401 pulisce la sessione Flask e marca
g.session_expired. Un solo after_request (app.py) trasforma le pagine HTML
rimaste in piedi con un errore generico in un redirect al login con flash;
le route JSON/AJAX che già inoltrano il dict di api_client (measure.py,
statistics.py) ricevono session_expired nel corpo insieme al 401.
Backend (riuso della key, bcrypt a tempo costante) resta al lotto A2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 22:33:40 +02:00
parent ac9bc14f5d
commit 1fee29358f
5 changed files with 155 additions and 7 deletions
+24 -2
View File
@@ -2,7 +2,7 @@
from typing import Any
import requests
from flask import has_request_context, request, session
from flask import g, has_request_context, request, session
from config import Config
@@ -39,6 +39,21 @@ class APIClient:
headers["X-Real-IP"] = client_ip
return headers
@staticmethod
def _handle_session_expired() -> None:
"""The backend says our api_key is no longer valid (#25).
One point for the whole app: whichever route made the call, the
Flask session is emptied here so nothing downstream keeps acting as
that user. A flag is left on ``g`` for the single after_request
hook in app.py to turn into a redirect-to-login on HTML pages; JSON
callers already get ``session_expired`` in the body returned below.
"""
if not has_request_context():
return
session.clear()
g.session_expired = True
def _handle_response(self, response: requests.Response) -> dict[str, Any]:
"""Parse response and return normalized dict.
@@ -46,12 +61,16 @@ class APIClient:
- If 2xx and not 204: response JSON
- If 204 No Content: empty dict
- If 4xx/5xx: {"error": True, "status_code": ..., "detail": "..."}
(401 also carries "session_expired": True)
"""
if response.ok:
if response.status_code == 204:
return {}
return response.json()
if response.status_code == 401:
self._handle_session_expired()
# Non-OK response (4xx/5xx)
try:
error_body = response.json()
@@ -74,11 +93,14 @@ class APIClient:
except Exception:
detail = response.text or f"HTTP {response.status_code}"
return {
result = {
"error": True,
"status_code": response.status_code,
"detail": detail
}
if response.status_code == 401:
result["session_expired"] = True
return result
def get(self, endpoint: str, params: dict | None = None) -> dict[str, Any]:
"""GET request to API server."""