Files
TieMeasureFlow/client/blueprints/auth.py
T
Adriano edd4580a5a feat: FASE 2 - Client Base (layout, login, navbar, tema, i18n)
Implementazione completa del frontend Flask:
- Layout base.html con TailwindCSS CDN, dark/light theme, flash messages
- Navbar responsive role-based (Maker, MeasurementTec, Metrologist, Admin)
- Login page professionale con form + API integration
- Profilo utente: nome, lingua, tema, badge ruoli
- Sistema tema dark/light: CSS variables + Alpine.js store + localStorage
- i18n completo IT/EN: Flask-Babel (.po) + alpinejs-i18n (JSON)
- API Client riscritto: error handling normalizzato, no crash su 4xx/5xx
- CSRF protection con Flask-WTF su tutti i form
- Logo aziendale dinamico da system_settings
- Asset SVG: tmflow-logo.svg + tmflow-icon.svg (favicon)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 01:10:13 +01:00

142 lines
5.0 KiB
Python

"""Authentication blueprint - login, logout, profile."""
from functools import wraps
from flask import Blueprint, flash, redirect, render_template, request, session, url_for
from flask_babel import gettext as _
from services.api_client import api_client
auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
def login_required(f):
"""Decorator to require login for protected routes."""
@wraps(f)
def decorated(*args, **kwargs):
if not session.get("api_key"):
flash(_("Effettua il login per continuare"), "warning")
return redirect(url_for("auth.login"))
return f(*args, **kwargs)
return decorated
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
"""Login page and form handler."""
# Se già loggato, redirect a home
if session.get("api_key"):
try:
return redirect(url_for("measure.select_recipe"))
except Exception:
return redirect(url_for("auth.profile"))
if request.method == "POST":
username = request.form.get("username", "").strip()
password = request.form.get("password", "")
if not username or not password:
flash(_("Inserisci username e password"), "error")
return render_template("auth/login.html")
try:
response = api_client.post("/api/auth/login", data={"username": username, "password": password})
if response.get("error"):
flash(response.get("detail", _("Credenziali non valide")), "error")
return render_template("auth/login.html")
# Salva in sessione
session["api_key"] = response["api_key"]
session["user"] = response["user"]
session["user_id"] = response["user"]["id"]
# Applica preferenze utente
user = response["user"]
if user.get("language_pref"):
session["language"] = user["language_pref"]
if user.get("theme_pref"):
session["theme"] = user["theme_pref"]
# Carica logo aziendale dalle impostazioni di sistema
settings_resp = api_client.get("/api/settings")
if not settings_resp.get("error"):
logo_path = settings_resp.get("company_logo_path")
if logo_path:
session["company_logo"] = logo_path
flash(_("Benvenuto, %(name)s!", name=user.get("display_name", username)), "success")
# Redirect dopo login
try:
return redirect(url_for("measure.select_recipe"))
except Exception:
return redirect(url_for("auth.profile"))
except Exception as e:
flash(_("Errore di connessione al server: %(error)s", error=str(e)), "error")
return render_template("auth/login.html")
return render_template("auth/login.html")
@auth_bp.route("/logout", methods=["GET", "POST"])
def logout():
"""Clear session and redirect to login."""
# Invalida il token sul server
if session.get("api_key"):
try:
api_client.post("/api/auth/logout")
except Exception:
pass # Ignora errori durante logout
session.clear()
flash(_("Logout effettuato"), "info")
return redirect(url_for("auth.login"))
@auth_bp.route("/profile", methods=["GET", "POST"])
@login_required
def profile():
"""User profile page - change display name, language, theme."""
if request.method == "POST":
display_name = request.form.get("display_name", "").strip()
language_pref = request.form.get("language_pref", "").strip()
theme_pref = request.form.get("theme_pref", "").strip()
try:
data = {}
if display_name:
data["display_name"] = display_name
if language_pref:
data["language_pref"] = language_pref
if theme_pref:
data["theme_pref"] = theme_pref
response = api_client.put("/api/auth/me", data=data)
if response.get("error"):
flash(response.get("detail", _("Errore durante l'aggiornamento del profilo")), "error")
else:
# Aggiorna sessione
session["user"] = response
if language_pref:
session["language"] = language_pref
if theme_pref:
session["theme"] = theme_pref
flash(_("Profilo aggiornato con successo"), "success")
return redirect(url_for("auth.profile"))
except Exception as e:
flash(_("Errore di connessione al server: %(error)s", error=str(e)), "error")
# GET - carica dati profilo
user_data = api_client.get("/api/auth/me")
if user_data.get("error"):
flash(_("Errore nel caricamento del profilo: %(error)s", error=user_data.get("detail", "")), "error")
user_data = session.get("user", {})
else:
session["user"] = user_data
return render_template("auth/profile.html", user=user_data)