feat: optional Bearer-token authentication via API_TOKEN env var
Disabled by default (empty API_TOKEN). When set: - All /api/* and /mcp requests require: Authorization: Bearer <token> - Public exemptions: /, /healthz, /static/*, /auth-check - Web UI: pre-flight /auth-check on load; shows token modal if required - Token stored in sessionStorage, sent on every API request - Mid-session 401s re-trigger the token modal - MCP clients must pass the header: Authorization: Bearer <token> Files changed: - app/config.py: api_token field + API_TOKEN env var - app/api/auth.py: Starlette BaseHTTPMiddleware for token enforcement - main.py: register middleware + /auth-check public endpoint - static/js/api.js: token storage, auth header, 401 handler hook - static/js/app.js: auth pre-flight, showTokenModal(), bootstrap() - static/css/components.css: .auth-overlay / .auth-card styles - README.md: API_TOKEN env var docs + MCP client header example
This commit is contained in:
9
main.py
9
main.py
@@ -16,6 +16,7 @@ from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from app.api import routes_config, routes_instructions, routes_status
|
||||
from app.api.auth import TokenAuthMiddleware
|
||||
from app.config import settings
|
||||
from app.database import init_db
|
||||
from app.logging_setup import configure_logging
|
||||
@@ -60,6 +61,9 @@ def create_app() -> FastAPI:
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# --- Token auth middleware (no-op when API_TOKEN is not set) ---
|
||||
app.add_middleware(TokenAuthMiddleware, token=settings.api_token)
|
||||
|
||||
# --- Global exception handler ---
|
||||
@app.exception_handler(Exception)
|
||||
async def global_exception_handler(request: Request, exc: Exception):
|
||||
@@ -69,6 +73,11 @@ def create_app() -> FastAPI:
|
||||
content={"detail": "Internal server error", "error": str(exc)},
|
||||
)
|
||||
|
||||
# --- Public: lets the UI know whether it needs a token (no auth required) ---
|
||||
@app.get("/auth-check", include_in_schema=False)
|
||||
def auth_check():
|
||||
return {"auth_required": bool(settings.api_token)}
|
||||
|
||||
# --- API routers ---
|
||||
app.include_router(routes_status.router)
|
||||
app.include_router(routes_instructions.router)
|
||||
|
||||
Reference in New Issue
Block a user