Files
sriphat-dataplatform/03-apiservice/app/main.py
jigoong 3a5f9e9001 feat: replace SQLAdmin with Keycloak-protected API management page
- Disable SQLAdmin basic auth (comment out mount_admin, statics, redirect)
- Add /api-management page (Keycloak admin role required)
- Add admin_api_keys.py: REST endpoints for list/create clients and keys
- Add api_management.html: manage API clients, keys, permissions with copy-once key display
- Update index.html: API Management link -> /api-management
- Update auth middleware: add /api-management and /admin/users to PROTECTED_PATHS
- Add CHANGES-2026-06-04.md dev notes
2026-06-04 18:22:22 +07:00

101 lines
3.6 KiB
Python

from contextlib import asynccontextmanager
import logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.datastructures import Headers
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.sessions import SessionMiddleware
from app.api.v1.routes import router as v1_router
from app.routes.pages import router as pages_router
from app.routes.auth import router as auth_router
from app.routes.admin_users import router as admin_users_router
from app.routes.admin_api_keys import router as admin_api_keys_router
from app.middleware.auth_middleware import WebAuthenticationMiddleware
from app.core.config import settings
from app.db.init_db import init_db
# Configure logging for better error visibility
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logging.getLogger("uvicorn.error").setLevel(logging.DEBUG)
logging.getLogger("uvicorn.access").setLevel(logging.INFO)
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
class ForceHTTPSMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
request.scope["scheme"] = "https"
response = await call_next(request)
return response
class ForwardedProtoMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] in {"http", "websocket"}:
headers = Headers(scope=scope)
forwarded_proto = headers.get("x-forwarded-proto")
if forwarded_proto:
proto = forwarded_proto.split(",", 1)[0].strip()
if proto:
new_scope = dict(scope)
new_scope["scheme"] = proto
return await self.app(new_scope, receive, send)
return await self.app(scope, receive, send)
origins = [
"http://localhost:8040",
"https://ai.sriphat.com",
"http://ai.sriphat.com",
]
@asynccontextmanager
async def lifespan(_: FastAPI):
init_db()
yield
app = FastAPI(title=settings.APP_NAME, root_path=settings.ROOT_PATH, lifespan=lifespan)
# Add exception handler to log all errors with traceback
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
import traceback
logging.error(f"Unhandled exception: {exc}")
logging.error(traceback.format_exc())
from starlette.responses import JSONResponse
return JSONResponse(
status_code=500,
content={"detail": "Internal server error", "error": str(exc)}
)
# Middleware order is important! They execute in reverse order (LIFO)
# WebAuthenticationMiddleware needs SessionMiddleware, so SessionMiddleware must be added AFTER
app.add_middleware(ForceHTTPSMiddleware)
app.add_middleware(ForwardedProtoMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Add web authentication middleware (protects /, /docs, /data-management/* only)
# API endpoints (/api/v1/*) continue to use API Key authentication
app.add_middleware(WebAuthenticationMiddleware)
# SessionMiddleware must be added AFTER middlewares that use it (due to LIFO execution)
app.add_middleware(SessionMiddleware, secret_key=settings.ADMIN_SECRET_KEY)
app.include_router(v1_router) # API endpoints - use API Key auth
app.include_router(pages_router) # Web pages - use Keycloak auth
app.include_router(auth_router) # Authentication routes
app.include_router(admin_users_router) # Admin user management API
app.include_router(admin_api_keys_router) # API key management - use Keycloak admin auth