From e2cea50d87ee26461abc3e5c57bb4eefe5e1bbb1 Mon Sep 17 00:00:00 2001 From: Tomas Kracmar Date: Mon, 27 Apr 2026 10:09:44 +0200 Subject: [PATCH] hotfix(v1.7.9): auth diagnostics and rate-limit exemptions - Exempt /api/config/auth, /api/config/features, /health, /metrics from rate limiting - Fix generic exception handler to return proper JSON for HTTPException instead of re-raising - Add startup log with auth_enabled and version - Add frontend console logging for auth config fetch errors - Show 'Auth: OFF' or 'Auth: misconfigured' on auth button instead of empty text - Add backend debug logging to /api/config/auth endpoint --- VERSION | 2 +- backend/frontend/index.html | 26 ++++++++++++++++++++------ backend/main.py | 21 +++++++++++++++++---- backend/routes/config.py | 3 +++ 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index 84298f9..f65dc1e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.8 +1.7.9 diff --git a/backend/frontend/index.html b/backend/frontend/index.html index 71e814a..d49c4bc 100644 --- a/backend/frontend/index.html +++ b/backend/frontend/index.html @@ -591,9 +591,15 @@ async initAuth() { try { const res = await fetch('/api/config/auth'); - this.authConfig = await res.json(); - } catch { - this.authConfig = { auth_enabled: false }; + if (!res.ok) { + console.error('Auth config fetch failed:', res.status, res.statusText); + this.authConfig = { auth_enabled: false, _error: res.status }; + } else { + this.authConfig = await res.json(); + } + } catch (err) { + console.error('Auth config fetch error:', err); + this.authConfig = { auth_enabled: false, _error: 'network' }; } try { @@ -614,7 +620,17 @@ } if (!this.authConfig?.auth_enabled) { - this.authBtnText = ''; + this.authBtnText = 'Auth: OFF'; + console.warn('AOC auth is disabled. Set AUTH_ENABLED=true in .env to enable login.'); + return; + } + + const tenantId = this.authConfig.tenant_id; + const clientId = this.authConfig.client_id; + if (!clientId || !tenantId) { + this.authBtnText = 'Auth: misconfigured'; + this.statusText = 'Auth is enabled but client_id or tenant_id is missing. Check .env configuration.'; + console.error('AOC auth misconfigured: missing client_id or tenant_id in /api/config/auth'); return; } @@ -623,8 +639,6 @@ return; } - const tenantId = this.authConfig.tenant_id; - const clientId = this.authConfig.client_id; const baseScope = this.authConfig.scope || ""; this.authScopes = Array.from(new Set(['openid', 'profile', 'email', ...baseScope.split(/[ ,]+/).filter(Boolean)])); const authority = `https://login.microsoftonline.com/${tenantId}`; diff --git a/backend/main.py b/backend/main.py index 15da6d6..e3f9017 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,6 @@ import asyncio import logging +import os import time from contextlib import suppress from pathlib import Path @@ -104,7 +105,9 @@ async def cache_control_middleware(request: Request, call_next): @app.middleware("http") async def rate_limit_middleware(request: Request, call_next): """Apply Redis-backed rate limiting before processing the request.""" - if request.url.path.startswith("/api/"): + # Exempt config and health endpoints from rate limiting + exempt_paths = {"/api/config/auth", "/api/config/features", "/health", "/metrics"} + if request.url.path.startswith("/api/") and request.url.path not in exempt_paths: from rate_limiter import check_rate_limit await check_rate_limit(request) @@ -168,8 +171,6 @@ async def metrics(): @app.get("/api/version") async def version(): - import os - return {"version": os.environ.get("VERSION", "unknown")} @@ -177,7 +178,13 @@ async def version(): async def generic_exception_handler(request: Request, exc: Exception): """Return generic error messages for unhandled exceptions to avoid info leakage.""" if isinstance(exc, HTTPException): - raise exc + from fastapi.responses import JSONResponse + + return JSONResponse( + status_code=exc.status_code, + content={"detail": exc.detail}, + headers=getattr(exc, "headers", None) or {}, + ) logger.error("Unhandled exception", path=request.url.path, error=str(exc)) return Response( content='{"detail":"Internal server error"}', @@ -206,6 +213,12 @@ async def start_periodic_fetch(): from rules import seed_default_rules seed_default_rules() + logger.info( + "AOC startup", + version=os.environ.get("VERSION", "unknown"), + auth_enabled=AUTH_ENABLED, + ai_enabled=AI_FEATURES_ENABLED, + ) if ENABLE_PERIODIC_FETCH: app.state.fetch_task = asyncio.create_task(_periodic_fetch()) diff --git a/backend/routes/config.py b/backend/routes/config.py index ce00d83..949215e 100644 --- a/backend/routes/config.py +++ b/backend/routes/config.py @@ -1,3 +1,4 @@ +import structlog from config import ( AI_FEATURES_ENABLED, AUTH_CLIENT_ID, @@ -9,10 +10,12 @@ from config import ( from fastapi import APIRouter router = APIRouter() +logger = structlog.get_logger("aoc.config") @router.get("/config/auth") def auth_config(): + logger.debug("Auth config requested", auth_enabled=AUTH_ENABLED) return { "auth_enabled": AUTH_ENABLED, "tenant_id": AUTH_TENANT_ID,