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:
@@ -591,9 +591,15 @@
|
|||||||
async initAuth() {
|
async initAuth() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/config/auth');
|
const res = await fetch('/api/config/auth');
|
||||||
this.authConfig = await res.json();
|
if (!res.ok) {
|
||||||
} catch {
|
console.error('Auth config fetch failed:', res.status, res.statusText);
|
||||||
this.authConfig = { auth_enabled: false };
|
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 {
|
try {
|
||||||
@@ -614,7 +620,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.authConfig?.auth_enabled) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,8 +639,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenantId = this.authConfig.tenant_id;
|
|
||||||
const clientId = this.authConfig.client_id;
|
|
||||||
const baseScope = this.authConfig.scope || "";
|
const baseScope = this.authConfig.scope || "";
|
||||||
this.authScopes = Array.from(new Set(['openid', 'profile', 'email', ...baseScope.split(/[ ,]+/).filter(Boolean)]));
|
this.authScopes = Array.from(new Set(['openid', 'profile', 'email', ...baseScope.split(/[ ,]+/).filter(Boolean)]));
|
||||||
const authority = `https://login.microsoftonline.com/${tenantId}`;
|
const authority = `https://login.microsoftonline.com/${tenantId}`;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -104,7 +105,9 @@ async def cache_control_middleware(request: Request, call_next):
|
|||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
async def rate_limit_middleware(request: Request, call_next):
|
async def rate_limit_middleware(request: Request, call_next):
|
||||||
"""Apply Redis-backed rate limiting before processing the request."""
|
"""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
|
from rate_limiter import check_rate_limit
|
||||||
|
|
||||||
await check_rate_limit(request)
|
await check_rate_limit(request)
|
||||||
@@ -168,8 +171,6 @@ async def metrics():
|
|||||||
|
|
||||||
@app.get("/api/version")
|
@app.get("/api/version")
|
||||||
async def version():
|
async def version():
|
||||||
import os
|
|
||||||
|
|
||||||
return {"version": os.environ.get("VERSION", "unknown")}
|
return {"version": os.environ.get("VERSION", "unknown")}
|
||||||
|
|
||||||
|
|
||||||
@@ -177,7 +178,13 @@ async def version():
|
|||||||
async def generic_exception_handler(request: Request, exc: Exception):
|
async def generic_exception_handler(request: Request, exc: Exception):
|
||||||
"""Return generic error messages for unhandled exceptions to avoid info leakage."""
|
"""Return generic error messages for unhandled exceptions to avoid info leakage."""
|
||||||
if isinstance(exc, HTTPException):
|
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))
|
logger.error("Unhandled exception", path=request.url.path, error=str(exc))
|
||||||
return Response(
|
return Response(
|
||||||
content='{"detail":"Internal server error"}',
|
content='{"detail":"Internal server error"}',
|
||||||
@@ -206,6 +213,12 @@ async def start_periodic_fetch():
|
|||||||
from rules import seed_default_rules
|
from rules import seed_default_rules
|
||||||
|
|
||||||
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:
|
if ENABLE_PERIODIC_FETCH:
|
||||||
app.state.fetch_task = asyncio.create_task(_periodic_fetch())
|
app.state.fetch_task = asyncio.create_task(_periodic_fetch())
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import structlog
|
||||||
from config import (
|
from config import (
|
||||||
AI_FEATURES_ENABLED,
|
AI_FEATURES_ENABLED,
|
||||||
AUTH_CLIENT_ID,
|
AUTH_CLIENT_ID,
|
||||||
@@ -9,10 +10,12 @@ from config import (
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
logger = structlog.get_logger("aoc.config")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/config/auth")
|
@router.get("/config/auth")
|
||||||
def auth_config():
|
def auth_config():
|
||||||
|
logger.debug("Auth config requested", auth_enabled=AUTH_ENABLED)
|
||||||
return {
|
return {
|
||||||
"auth_enabled": AUTH_ENABLED,
|
"auth_enabled": AUTH_ENABLED,
|
||||||
"tenant_id": AUTH_TENANT_ID,
|
"tenant_id": AUTH_TENANT_ID,
|
||||||
|
|||||||
Reference in New Issue
Block a user