enhance logging and error handling in authentication process

This commit is contained in:
Eduardo Silva
2026-03-16 11:26:16 -03:00
parent a68820fd98
commit 66a3895eff
4 changed files with 41 additions and 4 deletions

View File

@@ -14,8 +14,15 @@ logger = logging.getLogger(__name__)
def _load_json(path: Path) -> dict | None: def _load_json(path: Path) -> dict | None:
if not path.exists(): if not path.exists():
return None return None
try:
with path.open("r", encoding="utf-8") as handle: with path.open("r", encoding="utf-8") as handle:
return json.load(handle) content = handle.read()
if not content.strip():
return None
return json.loads(content)
except json.JSONDecodeError:
logger.warning("Failed to parse '%s' — file may be mid-write, will retry on next request.", path.name)
return None
def _is_valid_provider_url(method_name: str, method: OIDCMethodModel) -> bool: def _is_valid_provider_url(method_name: str, method: OIDCMethodModel) -> bool:

View File

@@ -1,3 +1,5 @@
import logging
import time
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
@@ -8,11 +10,12 @@ from auth_gateway.settings import settings
from auth_gateway.storage.sqlite import SQLiteStorage from auth_gateway.storage.sqlite import SQLiteStorage
from auth_gateway.web.auth_routes import router as auth_router from auth_gateway.web.auth_routes import router as auth_router
from auth_gateway.web.login_routes import router as login_router from auth_gateway.web.login_routes import router as login_router
from fastapi import FastAPI from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
BASE_DIR = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent
_access_logger = logging.getLogger("uvicorn.error")
@asynccontextmanager @asynccontextmanager
@@ -27,6 +30,20 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="Auth Gateway", lifespan=lifespan) app = FastAPI(title="Auth Gateway", lifespan=lifespan)
@app.middleware("http")
async def access_log(request: Request, call_next):
start = time.monotonic()
response = await call_next(request)
if request.url.path == "/auth/check" and response.status_code == 200:
return response
ms = (time.monotonic() - start) * 1000
client = request.client.host if request.client else "-"
_access_logger.info('%s - "%s %s" %d (%.0fms)', client, request.method, request.url.path, response.status_code, ms)
return response
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static") app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
app.include_router(auth_router) app.include_router(auth_router)
app.include_router(login_router) app.include_router(login_router)

View File

@@ -1,4 +1,8 @@
import logging
from auth_gateway.models.auth import OIDCMethodModel from auth_gateway.models.auth import OIDCMethodModel
logger = logging.getLogger("uvicorn.error")
from auth_gateway.services.oidc_service import is_oidc_identity_allowed from auth_gateway.services.oidc_service import is_oidc_identity_allowed
from auth_gateway.services.password_service import verify_user_password from auth_gateway.services.password_service import verify_user_password
from auth_gateway.services.policy_engine import evaluate_ip_access, extract_client_ip from auth_gateway.services.policy_engine import evaluate_ip_access, extract_client_ip
@@ -121,8 +125,10 @@ async def login_password_submit(request: Request, next: str = Form("/"), usernam
user = verify_user_password(username, password, runtime_config.users) user = verify_user_password(username, password, runtime_config.users)
if not user: if not user:
logger.warning("AUTH password failed for '%s' (policy: %s)", username, context.policy_name)
return _render(request, "login_password.html", status_code=401, next=next, application_name=context.application.name, error="Invalid username or password.") return _render(request, "login_password.html", status_code=401, next=next, application_name=context.application.name, error="Invalid username or password.")
if effective_policy.allowed_users and username not in effective_policy.allowed_users: if effective_policy.allowed_users and username not in effective_policy.allowed_users:
logger.warning("AUTH password denied for '%s' — not in allowed_users (policy: %s)", username, context.policy_name)
return _render(request, "login_password.html", status_code=403, next=next, application_name=context.application.name, error="This user is not allowed by the active policy.") return _render(request, "login_password.html", status_code=403, next=next, application_name=context.application.name, error="This user is not allowed by the active policy.")
groups = [ groups = [
@@ -141,7 +147,9 @@ async def login_password_submit(request: Request, next: str = Form("/"), usernam
) )
if effective_policy.totp_method_names: if effective_policy.totp_method_names:
logger.info("AUTH password ok for '%s' → totp required (policy: %s)", username, context.policy_name)
return _redirect_with_cookie(request, build_external_url(request, "/login/totp", next=next), session) return _redirect_with_cookie(request, build_external_url(request, "/login/totp", next=next), session)
logger.info("AUTH login ok for '%s' (policy: %s)", username, context.policy_name)
return _redirect_with_cookie(request, next, session) return _redirect_with_cookie(request, next, session)
@@ -182,6 +190,7 @@ async def login_totp_submit(request: Request, next: str = Form("/"), token: str
secret = user.totp_secret secret = user.totp_secret
if not verify_totp(secret, token): if not verify_totp(secret, token):
logger.warning("AUTH totp failed for '%s' (policy: %s)", session.username if session else "?", context.policy_name)
return _render(request, "login_totp.html", status_code=401, next=next, application_name=context.application.name, error="Invalid verification code.") return _render(request, "login_totp.html", status_code=401, next=next, application_name=context.application.name, error="Invalid verification code.")
session_service = request.app.state.session_service session_service = request.app.state.session_service
@@ -190,6 +199,7 @@ async def login_totp_submit(request: Request, next: str = Form("/"), token: str
add_factors=["totp"], add_factors=["totp"],
expires_in_minutes=get_effective_expiration(request, effective_policy, ["totp"]), expires_in_minutes=get_effective_expiration(request, effective_policy, ["totp"]),
) )
logger.info("AUTH login ok for '%s' via password+totp (policy: %s)", refreshed_session.username, context.policy_name)
return _redirect_with_cookie(request, next, refreshed_session) return _redirect_with_cookie(request, next, refreshed_session)
@@ -246,7 +256,10 @@ async def login_oidc_callback(request: Request, state: str):
def _do_logout(request: Request, next_url: str = "/") -> RedirectResponse: def _do_logout(request: Request, next_url: str = "/") -> RedirectResponse:
session_cookie = request.cookies.get(request.app.state.settings.cookie_name) session_cookie = request.cookies.get(request.app.state.settings.cookie_name)
session = request.app.state.session_service.get_session(session_cookie)
request.app.state.session_service.delete_session(session_cookie) request.app.state.session_service.delete_session(session_cookie)
if session:
logger.info("AUTH logout for '%s'", session.username or session.email or "unknown")
response = RedirectResponse(next_url or "/", status_code=303) response = RedirectResponse(next_url or "/", status_code=303)
response.delete_cookie(request.app.state.settings.cookie_name, path="/") response.delete_cookie(request.app.state.settings.cookie_name, path="/")
return response return response

View File

@@ -2,4 +2,4 @@
set -eu set -eu
exec uvicorn auth_gateway.main:app --host 0.0.0.0 --port 9091 exec uvicorn auth_gateway.main:app --host 0.0.0.0 --port 9091 --no-access-log