From: Giorgio Ravera Date: Fri, 23 Jan 2026 23:25:13 +0000 (+0100) Subject: Added main instead of uvicorn, fixed logout issue & added logout log X-Git-Tag: v0.0.1~30 X-Git-Url: http://git.giorgioravera.it/?a=commitdiff_plain;h=5940bbf618ef57f09963f172a0798a00f73487b7;p=network-manager.git Added main instead of uvicorn, fixed logout issue & added logout log --- diff --git a/Dockerfile b/Dockerfile index 8cbc78c..c84995b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/backend/main.py b/backend/main.py index 8bde8df..147877a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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 + ) diff --git a/backend/routes/login.py b/backend/routes/login.py index 5a37034..4a8789b 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -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"} diff --git a/backend/security.py b/backend/security.py index 0432584..f0792d5 100644 --- a/backend/security.py +++ b/backend/security.py @@ -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")