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"
},
)
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)
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",
+ }
// Disabilita UI + spinner
const originalBtnHTML = btn.innerHTML;
- if(btn) {
+ if(btn) {
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
btn.innerHTML = `
`;
}
-
try {
const fetchPromise = fetch('/api/login', {
method: 'POST',
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;
}