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
This commit is contained in:
2026-04-27 10:09:44 +02:00
parent 7fe53f882a
commit 5b4f93a6ae
4 changed files with 41 additions and 11 deletions

View File

@@ -1 +1 @@
1.7.8
1.7.9

View File

@@ -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}`;

View File

@@ -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())

View File

@@ -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,