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
+18 -1
View File
@@ -5,7 +5,7 @@ import re
from datetime import date
from urllib.parse import urlparse
from flask import Flask, flash, jsonify, redirect, url_for, session, request
from flask import Flask, flash, g, jsonify, redirect, url_for, session, request
from flask_babel import Babel, gettext as _
from flask_wtf.csrf import CSRFError, CSRFProtect
from markupsafe import Markup, escape
@@ -186,6 +186,23 @@ def create_app() -> Flask:
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
return response
@app.after_request
def handle_expired_session(response):
"""Turn a 401 from the backend into a redirect, for page loads (#25).
api_client already emptied the session and set ``g.session_expired``
on any 401. JSON/AJAX proxy routes already forward the api_client
dict (which carries ``session_expired``) as their own body, so a
fetch caller has what it needs. What is left is the page views that
swallowed the error (flashed something generic and rendered anyway,
e.g. an empty recipe list): those get replaced here, in the one
place that sees every response, instead of one check per route.
"""
if getattr(g, "session_expired", False) and not response.is_json:
flash(_("Sessione scaduta: effettua nuovamente l'accesso."), "error")
return redirect(url_for("auth.login"))
return response
@app.context_processor
def inject_globals():
"""Inject global variables into all templates."""