]> git.giorgioravera.it Git - network-manager.git/commitdiff
Created login router
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 5 Jan 2026 19:32:37 +0000 (20:32 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 5 Jan 2026 19:32:37 +0000 (20:32 +0100)
backend/config.py
backend/main.py
backend/routes/health.py [new file with mode: 0644]
backend/routes/login.py [new file with mode: 0644]
backend/security.py [new file with mode: 0644]

index edee14ea9a6480f8784c06aa5a4a5c4932ae0e79..f7ee9988e17879910212250174b55d14d69f9020 100644 (file)
@@ -6,6 +6,9 @@ import secrets
 # Import local modules
 from backend.utils import load_hash
 
+# Frontend related settings
+FRONTEND_DIR = "/app/frontend"
+
 # Database related settings
 DB_FILE = os.getenv("DB_FILE", "/data/database.db")
 DB_RESET = os.getenv("DB_RESET", "0") == "1"
index 4b3a9bc86b50073dfe6253dd97196422ff5e1a22..28ce840948f5fc2c9d508f6133a870299b1907cd 100644 (file)
@@ -5,11 +5,10 @@ from fastapi import FastAPI
 from fastapi import Request, Response, HTTPException
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import FileResponse, RedirectResponse
-from itsdangerous import TimestampSigner
 import os
 import ipaddress
-import time
 # Import local modules
+from backend.security import is_logged_in, require_login
 from backend.db.hosts import (
     get_hosts,
     get_host,
@@ -17,20 +16,15 @@ from backend.db.hosts import (
     update_host,
     delete_host
 )
-from backend.db.users import (
-    verify_login
-)
+from backend.routes.health import router as health_router
+from backend.routes.login import router as login_router
 # Import config variables
-from backend.config import SECRET_KEY
-from backend.config import HTTP_PORT
-from backend.config import LOGIN_MAX_ATTEMPTS
-from backend.config import LOGIN_WINDOW_SECONDS
-
-# IP → lista timestamp tentativi 
-login_attempts = {}
+from backend.config import FRONTEND_DIR, HTTP_PORT
 
 # Start FastAPI app
 app = FastAPI()
+app.include_router(health_router)
+app.include_router(login_router)
 
 # Allow frontend JS to call the API
 app.add_middleware(
@@ -43,77 +37,31 @@ app.add_middleware(
     allow_headers=["Content-Type"],
 )
 
-# Token signer for session management
-signer = TimestampSigner(SECRET_KEY)
-
-def require_login(request: Request):
-    token = request.cookies.get("session")
-    if not token:
-        raise HTTPException(status_code=401, detail="Not authenticated")
-    try:
-        signer.unsign(token, max_age=86400)
-    except:
-        raise HTTPException(status_code=401, detail="Invalid session")
-
-def check_rate_limit(ip: str):
-    now = time.time()
-
-    attempts = login_attempts.get(ip, [])
-    # tieni solo tentativi negli ultimi LOGIN_WINDOW_SECONDS secondi
-    attempts = [t for t in attempts if now - t < LOGIN_WINDOW_SECONDS]
-
-    if len(attempts) >= LOGIN_MAX_ATTEMPTS:
-        raise HTTPException(status_code=429, detail="Too many login attempts")
-
-    # registra nuovo tentativo
-    attempts.append(now)
-    login_attempts[ip] = attempts
-
 # ---------------------------------------------------------
 # FRONTEND PATHS (absolute paths inside Docker)
 # ---------------------------------------------------------
 
-FRONTEND_DIR = "/app/frontend"
+# Protect html pages
+def html_protected(request: Request, filename: str):
+    if not is_logged_in(request):
+        return RedirectResponse("/login")
+    return FileResponse(os.path.join(FRONTEND_DIR, filename))
 
 # Homepage
 @app.get("/")
 def home(request: Request):
-    token = request.cookies.get("session")
-    if not token:
-        return RedirectResponse("/login")
-    try:
-        signer.unsign(token, max_age=86400)  # 24h
-    except:
-        return RedirectResponse("/login")
-    return FileResponse(os.path.join(FRONTEND_DIR, "hosts.html"))
-
-# Login
-@app.get("/login")
-def login_page():
-    return FileResponse(os.path.join(FRONTEND_DIR, "login.html"))
+    return html_protected(request, "hosts.html")
 
-# Hosts management
+# Hosts page
 @app.get("/hosts")
 def hosts(request: Request):
-    token = request.cookies.get("session")
-    if not token:
-        return RedirectResponse("/login")
-    try:
-        signer.unsign(token, max_age=86400)
-    except:
-        return RedirectResponse("/login")
-    return FileResponse(os.path.join(FRONTEND_DIR, "hosts.html"))
+    return html_protected(request, "hosts.html")
 
 # Serve hosts.css
 @app.get("/css/hosts.css")
 def css_hosts():
     return FileResponse(os.path.join(FRONTEND_DIR, "css/hosts.css"))
 
-# Serve login.css
-@app.get("/css/login.css")
-def css_login():
-    return FileResponse(os.path.join(FRONTEND_DIR, "css/login.css"))
-
 # Serve app.js
 @app.get("/app.js")
 def js():
@@ -123,36 +71,6 @@ def js():
 # API ENDPOINTS
 # ---------------------------------------------------------
 
-@app.post("/api/login")
-def api_login(request: Request, data: dict, response: Response):
-    ip = request.client.host 
-    check_rate_limit(ip)
-
-    user = data.get("username")
-    pwd = data.get("password")
-    if (verify_login(user, pwd)):
-    #if user == "admin" and pwd == "admin":
-        # 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" # riduce CSRF
-        )
-        return {"status": "ok"}
-    return {"error": "Invalid credentials"}
-
-@app.post("/api/logout")
-def api_logout(response: Response):
-    response.delete_cookie("session")
-    return {"status": "logged_out"}
-
 @app.get("/api/hosts")
 def api_get_hosts(request: Request):
     require_login(request)
diff --git a/backend/routes/health.py b/backend/routes/health.py
new file mode 100644 (file)
index 0000000..98af0bc
--- /dev/null
@@ -0,0 +1,9 @@
+# backend/health.py
+
+from fastapi import APIRouter
+
+router = APIRouter()
+
+@router.get("/health", tags=["health"])
+def health_check():
+    return {"status": "ok"}
diff --git a/backend/routes/login.py b/backend/routes/login.py
new file mode 100644 (file)
index 0000000..7918ea1
--- /dev/null
@@ -0,0 +1,81 @@
+# backend/routes/login.py
+
+# import standard modules
+from fastapi import APIRouter, Request, Response
+from fastapi.responses import FileResponse, RedirectResponse
+import os
+import time
+# Import local modules
+from backend.security import is_logged_in, signer
+from backend.db.users import verify_login
+# Import config variables
+from backend.config import FRONTEND_DIR, LOGIN_MAX_ATTEMPTS, LOGIN_WINDOW_SECONDS
+
+router = APIRouter()
+
+# IP -> lista timestamp tentativi 
+login_attempts = {}
+
+def check_rate_limit(ip: str):
+    now = time.time()
+    attempts = login_attempts.get(ip, [])
+    # tieni solo tentativi negli ultimi LOGIN_WINDOW_SECONDS secondi
+    attempts = [t for t in attempts if now - t < LOGIN_WINDOW_SECONDS]
+
+    if len(attempts) >= LOGIN_MAX_ATTEMPTS:
+        raise HTTPException(status_code=429, detail="Too many login attempts")
+
+    # registra nuovo tentativo
+    attempts.append(now)
+    login_attempts[ip] = attempts
+
+# ---------------------------------------------------------
+# FRONTEND PATHS (absolute paths inside Docker)
+# ---------------------------------------------------------
+
+# Login page
+@router.get("/login")
+def login_page(request: Request):
+    if is_logged_in(request):
+        return RedirectResponse("/")
+    return FileResponse(os.path.join(FRONTEND_DIR, "login.html"))
+
+# Serve login.css
+@router.get("/css/login.css")
+def css_login():
+    return FileResponse(os.path.join(FRONTEND_DIR, "css/login.css"))
+
+# ---------------------------------------------------------
+# API ENDPOINTS
+# ---------------------------------------------------------
+
+@router.post("/api/login")
+def api_login(request: Request, data: dict, response: Response):
+    ip = request.client.host
+    check_rate_limit(ip)
+
+    user = data.get("username")
+    pwd = data.get("password")
+
+    if verify_login(user, pwd):
+        # 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"
+        )
+        return {"status": "ok"}
+
+    return {"error": "Invalid credentials"}
+
+@router.post("/api/logout")
+def api_logout(response: Response):
+    response.delete_cookie("session")
+    return {"status": "logged_out"}
diff --git a/backend/security.py b/backend/security.py
new file mode 100644 (file)
index 0000000..803e2cf
--- /dev/null
@@ -0,0 +1,23 @@
+# backend/security.py
+
+# import standard modules
+from fastapi import Request, HTTPException
+from itsdangerous import TimestampSigner
+# Import config variables
+from backend.config import SECRET_KEY
+
+signer = TimestampSigner(SECRET_KEY)
+
+def is_logged_in(request: Request) -> bool:
+    token = request.cookies.get("session")
+    if not token:
+        return False
+    try:
+        signer.unsign(token, max_age=86400)
+        return True
+    except:
+        return False
+
+def require_login(request: Request):
+    if not is_logged_in(request):
+        raise HTTPException(status_code=401, detail="Not authenticated")