Files
shihaam 66073c7891
build-and-push / build (push) Failing after 35s
added multi user support, move api key to database
2026-08-01 13:16:54 +05:00

132 lines
3.4 KiB
Python

from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
from .auth import get_current_admin, record_log
from .bootstrap import seed_default_admin
from .config import get_settings
from .database import SessionLocal, engine
from .errors import APIError, api_error_handler
from .routers import (
apikeys,
auth,
client,
customers,
device,
logs,
nas,
nasreload,
radacct,
radcheck,
radgroupcheck,
radgroupreply,
radpostauth,
radreply,
radusergroup,
users,
vlan,
)
settings = get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Seed the default admin/admin account on first run.
seed_default_admin()
yield
app = FastAPI(
title="FreeRADIUS REST API",
description="RESTful CRUD over the FreeRADIUS SQL schema. Authenticate with a "
"login session (`Authorization: Bearer`) or an admin-issued `X-API-Key`.",
version="2.0.0",
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origin_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_exception_handler(APIError, api_error_handler)
# Management routers self-log with rich detail; skip them here to avoid dupes.
_SELF_LOGGED_PREFIXES = ("/auth", "/users", "/apikeys")
_LOGGED_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
@app.middleware("http")
async def activity_log_middleware(request: Request, call_next):
"""Record every successful state-changing request to radadmin_logs, attributed
to the user resolved by the auth dependency (stashed on request.state)."""
response = await call_next(request)
if request.method in _LOGGED_METHODS and response.status_code < 400:
path = request.url.path
if not path.startswith(_SELF_LOGGED_PREFIXES):
username = getattr(request.state, "username", None)
if username:
db = SessionLocal()
try:
record_log(
db,
username=username,
action=request.method.lower(),
detail=f"{request.method} {path}",
request=request,
)
finally:
db.close()
return response
# ---- unauthenticated meta endpoints ----
@app.get("/", tags=["meta"])
def root():
return {"service": "freeradius-rest-api", "docs": "/docs", "health": "/health"}
@app.get("/health", tags=["meta"])
def health():
try:
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
db_ok = True
except Exception:
db_ok = False
return {"status": "ok" if db_ok else "degraded", "database": "up" if db_ok else "down"}
# ---- auth + management routers (guard themselves per-endpoint) ----
app.include_router(auth.router)
app.include_router(users.router)
app.include_router(apikeys.router)
app.include_router(logs.router)
# ---- authenticated resource routers (any logged-in user or API key) ----
protected = [Depends(get_current_admin)]
for module in (
customers,
nas,
radcheck,
radreply,
radgroupcheck,
radgroupreply,
radusergroup,
vlan,
client,
device,
radacct,
radpostauth,
nasreload,
):
app.include_router(module.router, dependencies=protected)