]> git.giorgioravera.it Git - network-manager.git/commitdiff
Improved login json
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Thu, 12 Mar 2026 18:08:47 +0000 (19:08 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Thu, 12 Mar 2026 18:08:47 +0000 (19:08 +0100)
backend/routes/login.py
frontend/js/login.js

index d454b4a9ad84a520f3b81937ab22021d9701abb9..feda767b7790f82f056db8a2eb1b65cfc7414e6c 100644 (file)
@@ -27,9 +27,11 @@ def check_rate_limit(ip: str):
 
     if len(attempts) >= int(get_config("login_max_attempts")):
         raise HTTPException(
-            status_code=status.HTTP_409_CONFLICT,
+            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
             detail={
-                "error": "Too many login attempts"
+                "code": "LOGIN_ERROR",
+                "status": "failure",
+                "message": "Too many login attempts"
             },
         )
 
@@ -56,9 +58,15 @@ def css_login():
     return FileResponse(os.path.join(settings.FRONTEND_DIR, "js/session.js"))
 
 # ---------------------------------------------------------
-# API ENDPOINTS
+# Login API
 # ---------------------------------------------------------
-@router.post("/api/login")
+@router.post("/api/login", status_code=status.HTTP_200_OK, responses={
+    200: {"description": "Login successful"},
+    401: {"description": "Invalid credentials"},
+    403: {"description": "Account locked"}, # GRGR TBD
+    429: {"description": "Too many login attempts"},
+    500: {"description": "Internal server error"},
+})
 def api_login(request: Request, data: dict, response: Response):
     ip = request.client.host
     check_rate_limit(ip)
@@ -71,11 +79,33 @@ def api_login(request: Request, data: dict, response: Response):
         login_attempts.pop(ip, None)
 
         apply_session(response, username=user)
-        return {"status": "ok"}
+        response.status_code = status.HTTP_200_OK
+        return {
+            "code": "LOGIN_SUCCESS",
+            "status": "success",
+            "message": "Login successful",
+        }
+
+    raise HTTPException(
+        status_code=status.HTTP_401_UNAUTHORIZED,
+        detail={
+            "code": "LOGIN_ERROR",
+            "status": "failure",
+            "message": "Invalid credentials",
+        },
+    )
 
-    return {"error": "Wrong credentials"}
-
-@router.post("/api/logout")
+# ---------------------------------------------------------
+# Logout API
+# ---------------------------------------------------------
+@router.post("/api/logout", status_code=status.HTTP_200_OK, responses={
+    200: {"description": "Logout successful"}
+})
 def api_logout(response: Response):
     close_session(response)
-    return {"status": "logged_out"}
+    response.status_code = status.HTTP_200_OK
+    return {
+        "code": "LOGOUT_SUCCESS",
+        "status": "success",
+        "message": "Logout successful",
+    }
index 0985646554ba43db8e227baecba67a057ce7bab0..bf26deef6ef267b52fa31a046b55a20121189650 100644 (file)
@@ -74,7 +74,7 @@ document.addEventListener('DOMContentLoaded', () => {
 
         // Disabilita UI + spinner
         const originalBtnHTML = btn.innerHTML;
-       if(btn) {
+        if(btn) {
             btn.disabled = true;
             btn.setAttribute('aria-busy', 'true');
             btn.innerHTML = `
@@ -83,7 +83,6 @@ document.addEventListener('DOMContentLoaded', () => {
             `;
         }
 
-
         try {
             const fetchPromise = fetch('/api/login', {
                 method: 'POST',
@@ -93,60 +92,52 @@ document.addEventListener('DOMContentLoaded', () => {
                 signal: inFlightController.signal
             });
 
-
             // error after 5s?
             // await Promise.race([ fetchPromise, timeout(5000, inFlightController.signal) ]);
             const res = await fetchPromise;
 
-            // Parse JSON resiliente
-            let data = {};
+            // Check content-type to avoid parsing errors
+            const contentType = res.headers.get("content-type") || "";
+            if (!contentType.includes("application/json")) {
+                const err = new Error(`${res.status}: ${res.statusText}`);
+                err.status = res.status;
+                throw err;
+            }
+
+            // Check JSON
+            let data;
             try {
                 data = await res.json();
             } catch {
-                // Se il server non risponde con JSON valido
-                data = { status: 'error', error: 'Risposta non valida dal server.' };
+                throw new Error('Invalid JSON payload');
             }
 
-            // Gestione HTTP non-OK come errore
+            // Check JSON errors
             if (!res.ok) {
-                // Usa messaggio server se presente, altrimenti status
-                const errMsg = (typeof data?.error === 'string' && data.error.trim())
-                    ? data.error.trim()
-                    : `Errore ${res.status} durante il login.`;
-                throw new Error(errMsg);
+                const serverMsg = data?.detail?.message?.trim();
+                const base = `Error during login`;
+                const err = new Error(serverMsg ? `${base}: ${serverMsg}` : base);
+                err.status = res.status;
+                throw err;
             }
 
-            // Gestione logica dell'API
-            if (data?.status === 'ok') {
-                // Redirect alla pagina host
-                location.assign('/hosts');
-                return;
-            } else {
-                const msg = (typeof data?.error === 'string' && data.error.trim())
-                    ? data.error.trim()
-                    : 'Credenziali non valide.';
-                // Credenziali errate -> metti focus su username, marca entrambi
-                showError(msg, { focus: 'username', markUser: true, markPass: true });
-                return;
-            }
+            // Login Success
+            // Redirect to main page (hosts)
+            location.assign('/hosts');
+            return;
+
         } catch (err) {
-          // Se è stato abortito, non mostrare errori
-          //if (inFlightController?.signal.aborted) return;
-          // Errori di rete o eccezioni
-          const msg = err?.message || 'Errore di connessione. Riprova.';
-          // Se è un timeout personalizzato, messaggio ad hoc
-          if (msg === 'Timeout di rete') {
-               showError('Il server non risponde. Riprova tra poco.', { focus: 'username' });
-          } else {
-              showError(msg, { focus: 'username' });
-          }
+            // Se è stato abortito, non mostrare errori
+            //if (inFlightController?.signal.aborted) return;
+            // Errori di rete o eccezioni
+            showError(err?.message || 'Connection error, please retry.', { focus: 'username', markUser: true, markPass: true });
         } finally {
             // Ripristina bottone
             if(btn) {
-                 btn.disabled = false;
-                 btn.removeAttribute('aria-busy');
-                 btn.innerHTML = originalBtnHTML ?? 'Entra';
-           }
+                btn.disabled = false;
+                btn.removeAttribute('aria-busy');
+                btn.innerHTML = originalBtnHTML ?? 'Entra';
+            }
             // controller consumato
             inFlightController = null;
         }