]> git.giorgioravera.it Git - network-manager.git/commitdiff
Added main instead of uvicorn, fixed logout issue & added logout log
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Fri, 23 Jan 2026 23:25:13 +0000 (00:25 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Fri, 23 Jan 2026 23:25:13 +0000 (00:25 +0100)
Dockerfile
backend/main.py
backend/routes/login.py
backend/security.py

index 8cbc78ce08566c190426237e87b1a0bc17c1fc54..c84995b1d5a50b2b8ec776a467284ef3db3a5140 100644 (file)
@@ -54,4 +54,4 @@ ENV HTTP_PORT=8000
 EXPOSE ${HTTP_PORT}
 
 ENTRYPOINT ["/app/entrypoint.py"]
-CMD ["python3", "-u", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]
+CMD ["python3", "-u", "-m", "backend.main"]
index 8bde8df681ecf45a0b7fd6d5d0176f08e10dc3e6..147877a6d7d5e91044dd89a13f8a736b2647903f 100644 (file)
@@ -1,6 +1,7 @@
 # backend/main.py
 
 # import standard modules
+from contextlib import asynccontextmanager
 from fastapi import FastAPI, Request
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import FileResponse, RedirectResponse, JSONResponse, Response
@@ -25,28 +26,9 @@ setup_logging(level=settings.LOG_LEVEL, to_file=settings.LOG_TO_FILE, log_file=s
 logger = get_logger("backend.main")
 
 # ------------------------------------------------------------------------------
-# App init
-# ------------------------------------------------------------------------------
-app = FastAPI(
-    title=settings.APP_NAME,
-    version=settings.APP_VERSION,
-)
-
-# ------------------------------------------------------------------------------
-# Routers
-# ------------------------------------------------------------------------------
-app.include_router(about_router)
-app.include_router(health_router)
-app.include_router(login_router)
-app.include_router(hosts_router)
-
+# Welcome log
 # ------------------------------------------------------------------------------
-# Startup log
-# ------------------------------------------------------------------------------
-@app.on_event("startup")
-async def startup_log():
-    logger = get_logger(__name__)
-
+def print_welcome():
     safe_secret = "****" if settings.SECRET_KEY else "undefined"
     safe_admin_pwd = "****" if settings.ADMIN_PASSWORD else "undefined"
     safe_admin_hash = "****" if settings.ADMIN_PASSWORD_HASH else "undefined"
@@ -72,6 +54,48 @@ async def startup_log():
         settings.ADMIN_USER, safe_admin_pwd, safe_admin_hash, settings.ADMIN_PASSWORD_HASH_FILE
     )
 
+# ------------------------------------------------------------------------------
+# Shutdown log
+# ------------------------------------------------------------------------------
+def print_goodbye():
+    logger = get_logger("backend.main")
+
+    logger.info(
+        "Application %s shutting down | app_version=%s",
+        settings.APP_NAME, settings.APP_VERSION
+    )
+
+# ------------------------------------------------------------------------------
+# Lifespan for startup and shutdown events
+# ------------------------------------------------------------------------------
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    # STARTUP
+    print_welcome()
+
+    try:
+        yield
+    finally:
+        # SHUTDOWN
+        print_goodbye()
+
+# ------------------------------------------------------------------------------
+# App init
+# ------------------------------------------------------------------------------
+app = FastAPI(
+    title=settings.APP_NAME,
+    version=settings.APP_VERSION,
+    lifespan=lifespan,
+)
+
+# ------------------------------------------------------------------------------
+# Routers
+# ------------------------------------------------------------------------------
+app.include_router(about_router)
+app.include_router(health_router)
+app.include_router(login_router)
+app.include_router(hosts_router)
+
 # ------------------------------------------------------------------------------
 # CORS
 # ------------------------------------------------------------------------------
@@ -95,8 +119,13 @@ 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"):
+    # Excludes the login/logout methods
+    if path.startswith("/login") or path.startswith("/api/login") or \
+       path.startswith("/logout") or path.startswith("/api/logout"):
+        return await call_next(request)
+
+    # Excludes the about & health methods
+    if path.startswith("/about") or path.startswith("/api/health"):
         return await call_next(request)
 
     # Excludes static files
@@ -111,7 +140,8 @@ async def session_middleware(request: Request, call_next):
     # Protected APIs
     if path.startswith("/api"):
         if not is_logged_in(request):
-            return JSONResponse({"error": "Not authenticated"}, status_code=401)
+            logger.error("API access denied - not logged in")
+            return JSONResponse({"error": "Unauthorized"}, status_code=401)
         response = await call_next(request)
         # Sliding expiration
         apply_session(response, username=None, token=token)
@@ -144,3 +174,28 @@ def css_variables(request: Request):
 def css_layout(request: Request):
     return FileResponse(os.path.join(settings.FRONTEND_DIR, "css/layout.css"))
 
+# ------------------------------------------------------------------------------
+# Entry-point
+# ------------------------------------------------------------------------------
+if __name__ == "__main__":
+    import uvicorn
+
+    # Uvicorn config da settings con fallback
+    host = getattr(settings, "HTTP_HOST", "0.0.0.0")
+    port = int(getattr(settings, "HTTP_PORT", 8000))
+    reload_flag = bool(getattr(settings, "DEV_RELOAD", False))
+
+    logger.info(f"Server running on http://{host}:{port} (reload={reload_flag})")
+
+    uvicorn.run(
+        app,
+        host=host,
+        port=port,
+        reload=reload_flag,
+        proxy_headers=True,
+        forwarded_allow_ips="*",
+        log_level=(settings.LOG_LEVEL or "info").lower(),
+        #access_log=True,
+        log_config=None,
+        # workers=1,                # GRGR in prod valuta gunicorn+uvicorn workers
+    )
index 5a37034d6938111f56df78aff081e738dcfd54fa..4a8789bcda38cb333853d2157d85c8528aa60146 100644 (file)
@@ -6,7 +6,7 @@ from fastapi.responses import FileResponse, RedirectResponse
 import os
 import time
 # Import local modules
-from backend.security import verify_login, apply_session
+from backend.security import verify_login, apply_session, close_session
 # Import Settings
 from settings.settings import settings
 
@@ -74,5 +74,5 @@ def api_login(request: Request, data: dict, response: Response):
 
 @router.post("/api/logout")
 def api_logout(response: Response):
-    response.delete_cookie("session")
+    close_session(response)
     return {"status": "logged_out"}
index 0432584ea8cf01276c10e5e5dbd5dcb7a4b16020..f0792d5fde21ad368f39c1f9b9812cd6bae05d20 100644 (file)
@@ -21,18 +21,18 @@ def verify_login(username, password):
     logger = get_logger(__name__)
     user = get_user_by_username(username)
     if not user:
-        logger.error("LOGIN failed - user %s not found", username)
+        logger.error("Login failed - user %s not found", username)
         return False
 
     if user["status"] != "active":
-        logger.error("LOGIN Failed - user %s disabled", username)
+        logger.error("Login Failed - user %s disabled", username)
         return False
 
     if not bcrypt.checkpw(password.encode(), user["password_hash"].encode()):
-        logger.error("LOGIN Failed - password wrong for user %s", username)
+        logger.error("Login Failed - password wrong for user %s", username)
         return False
 
-    logger.info("LOGIN user %s", username)
+    logger.info("Login successful - user %s", username)
     return True
 
 # ----------------------------
@@ -44,14 +44,14 @@ def apply_session(response, username: str | None = None, token: str | None = Non
     # First Login
     if username is not None and token is None:
         token = signer.sign(username).decode()
-        logger.info("SESSION_CREATE - %s", username)
+        logger.info("Session created - %s", username)
 
     if username is None:
         username = signer.unsign(token, max_age=86400).decode()
-        logger.info("SESSION_UPDATE - %s", username)
+        logger.info("Session updated - %s", username)
 
     if username is None or token is None:
-        logger.error("SESSION_ERROR")
+        logger.error("Session Error - missing username or token")
         return
 
     response.set_cookie(
@@ -60,7 +60,7 @@ def apply_session(response, username: str | None = None, token: str | None = Non
         httponly=True,
         max_age=86400,
         path="/",
-        #secure=True, # solo via HTTPS
+        #secure=True, # GRGR solo via HTTPS
         samesite="Strict"
     )
 
@@ -78,8 +78,14 @@ def is_logged_in(request: Request) -> bool:
         return False
 
 # -----------------------------
-# check login
+# Close Session
 # -----------------------------
-def require_login(request: Request):
-    if not is_logged_in(request):
-        raise HTTPException(status_code=401, detail="Not authenticated")
+def close_session(response):
+    logger = get_logger(__name__)
+    
+    response.delete_cookie(
+        key="session",
+        path="/"
+    )
+
+    logger.info("Session closed")