]> git.giorgioravera.it Git - network-manager.git/commitdiff
Sliding cookie expiration
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Tue, 6 Jan 2026 15:46:17 +0000 (16:46 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Tue, 6 Jan 2026 15:46:17 +0000 (16:46 +0100)
backend/db/users.py
backend/main.py
backend/routes/login.py
backend/security.py
backend/utils.py

index cc99512b06ded7195694fd3e819cd8b51c012348..48a427cff0bc65c2db85140231b05996d33f7e34 100644 (file)
@@ -1,12 +1,10 @@
 # backend/db/users.py
 
 # Import standard modules
-import bcrypt
 import json
 import os
 # Import local modules
-from backend.db.db import get_db
-from backend.db.db import register_init
+from backend.db.db import get_db, register_init
 
 # ================================
 # Create hash password
@@ -95,19 +93,3 @@ def create_user(username, password_hash, email=None, is_admin=0, modules=None):
     ))
 
     conn.commit()
-
-# -----------------------------
-# Verify Login
-# -----------------------------
-def verify_login(username, password):
-    user = get_user_by_username(username)
-    if not user:
-        return False
-
-    if user["status"] != "active":
-        return False
-
-    if not bcrypt.checkpw(password.encode(), user["password_hash"].encode()):
-        return False
-
-    return True
index d7c4652633db54258e19b6e701742f9199cfaa75..1f4908fcf758bceac52ad1636140823d4343ccc5 100644 (file)
@@ -6,7 +6,7 @@ from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import FileResponse, RedirectResponse, JSONResponse
 import os
 # Import local modules
-from backend.security import is_logged_in
+from backend.security import is_logged_in, apply_session
 from backend.routes.health import router as health_router
 from backend.routes.login import router as login_router
 from backend.routes.hosts import router as hosts_router
@@ -37,6 +37,7 @@ app.add_middleware(
 @app.middleware("http")
 async def session_middleware(request: Request, call_next):
     path = request.url.path
+    token = request.cookies.get("session")
 
     # Excludes the login methods
     if path.startswith("/login") or path.startswith("/api/login"):
@@ -58,13 +59,20 @@ async def session_middleware(request: Request, call_next):
     if path.startswith("/api"):
         if not is_logged_in(request):
             return JSONResponse({"error": "Not authenticated"}, status_code=401)
-        return await call_next(request)
+
+        response = await call_next(request)
+        # Sliding expiration
+        apply_session(response, username=None, token=token)
+        return response
 
     # Protected HTML pages
     if not is_logged_in(request):
         return RedirectResponse("/login")
 
-    return await call_next(request)
+    response = await call_next(request)
+    # Sliding expiration
+    apply_session(response, username=None, token=token)
+    return response
 
 # ---------------------------------------------------------
 # FRONTEND PATHS (absolute paths inside Docker)
index adfb98ffbbc3a571aa2613fe61ccbaa2a9f354dc..7b6a6be7304ab478c3786ce72473b9a1778085c6 100644 (file)
@@ -6,8 +6,7 @@ from fastapi.responses import FileResponse, RedirectResponse
 import os
 import time
 # Import local modules
-from backend.security import signer
-from backend.db.users import verify_login
+from backend.security import verify_login, apply_session
 # Import config variables
 from backend.config import FRONTEND_DIR, LOGIN_MAX_ATTEMPTS, LOGIN_WINDOW_SECONDS
 
@@ -60,16 +59,7 @@ def api_login(request: Request, data: dict, response: Response):
         # reset tentativi su IP 
         login_attempts.pop(ip, None)
 
-        token = signer.sign(user).decode()
-        response.set_cookie(
-            "session",
-            token,
-            httponly=True,
-            max_age=86400,
-            path="/",
-            #secure=True, # solo via HTTPS
-            samesite="Strict"
-        )
+        apply_session(response, username=user)
         return {"status": "ok"}
 
     return {"error": "Wrong credentials"}
index a79148fb617c62260c901366920970c614f22162..504d9ac96fbaa88d608890a4500e8cfb32190faf 100644 (file)
@@ -1,14 +1,66 @@
 # backend/security.py
 
-# import standard modules
+# Import standard modules
+import bcrypt
 import os
 from fastapi import Request, HTTPException
 from itsdangerous import TimestampSigner
+# Import local modules
+from backend.db.users import get_user_by_username
+from backend.utils import log_event
 # Import config variables
 from backend.config import FRONTEND_DIR, SECRET_KEY
 
 signer = TimestampSigner(SECRET_KEY)
 
+# -----------------------------
+# Verify Login
+# -----------------------------
+def verify_login(username, password):
+    user = get_user_by_username(username)
+    if not user:
+        log_event("LOGIN failed - user not found", user=username)
+        return False
+
+    if user["status"] != "active":
+        log_event("LOGIN Failed - user disabled", user=username)
+        return False
+
+    if not bcrypt.checkpw(password.encode(), user["password_hash"].encode()):
+        log_event("LOGIN Failed - password wrong", user=username)
+        return False
+
+    log_event("LOGIN", user=username)
+    return True
+
+# ----------------------------
+# creates or renew the cookie
+# ----------------------------
+def apply_session(response, username: str | None = None, token: str | None = None):
+
+    # First Login
+    if username is not None and token is None:
+        token = signer.sign(username).decode()
+        log_event("SESSION_CREATE", user=username)
+
+    if username is None:
+        username = signer.unsign(token, max_age=86400).decode()
+        log_event("SESSION_UPDATE", user=username)
+
+    if username is None or token is None:
+        log_event("SESSION_ERROR")
+        return
+
+    response.set_cookie(
+        "session",
+        token,
+        httponly=True,
+        max_age=86400,
+        path="/",
+        #secure=True, # solo via HTTPS
+        samesite="Strict"
+    )
+
 # -----------------------------
 # check session cookie
 # -----------------------------
index 155124935776f47813222975099d570824c44571..b5e4cf86ff4ccc216ae99973712168ae38b6a661 100644 (file)
@@ -1,6 +1,7 @@
 # backend/db/utils.py
 
 # Import standard modules
+from datetime import datetime
 import os
 
 # -----------------------------
@@ -12,3 +13,12 @@ def load_hash(hash_file: str):
         with open(path, "r") as f:
             return f.read().strip()
     return None
+
+# -----------------------------
+# Log Event
+# -----------------------------
+def log_event(event: str, **fields):
+    ts = datetime.utcnow().isoformat() + "Z"
+    parts = " ".join(f"{k}={v}" for k, v in fields.items())
+    print(f"INFO:     {ts} {event} {parts}")
+