This commit is contained in:
+60
-8
@@ -1,15 +1,21 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import Depends, FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy import text
|
||||
|
||||
from .auth import require_api_key
|
||||
from .auth import get_current_admin, record_log
|
||||
from .bootstrap import seed_default_admin
|
||||
from .config import get_settings
|
||||
from .database import engine
|
||||
from .database import SessionLocal, engine
|
||||
from .errors import APIError, api_error_handler
|
||||
from .routers import (
|
||||
apikeys,
|
||||
auth,
|
||||
client,
|
||||
customers,
|
||||
device,
|
||||
logs,
|
||||
nas,
|
||||
nasreload,
|
||||
radacct,
|
||||
@@ -19,16 +25,26 @@ from .routers import (
|
||||
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. "
|
||||
"All endpoints require the `X-API-Key` header.",
|
||||
version="1.0.0",
|
||||
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(
|
||||
@@ -42,6 +58,35 @@ app.add_middleware(
|
||||
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():
|
||||
@@ -59,8 +104,15 @@ def health():
|
||||
return {"status": "ok" if db_ok else "degraded", "database": "up" if db_ok else "down"}
|
||||
|
||||
|
||||
# ---- authenticated resource routers ----
|
||||
protected = [Depends(require_api_key)]
|
||||
# ---- 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,
|
||||
|
||||
Reference in New Issue
Block a user