2025-08-12 13:24:27 -07:00

89 lines
2.6 KiB
Python

import contextlib
import logging
from pathlib import Path
from fastapi import Cookie, FastAPI, Request, Response
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from ..authsession import get_session
from . import ws
from .api import register_api_routes
from .reset import register_reset_routes
STATIC_DIR = Path(__file__).parent.parent / "frontend-build"
app = FastAPI()
# Global exception handlers
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
"""Handle ValueError exceptions globally with 400 status code."""
return JSONResponse(status_code=400, content={"detail": str(exc)})
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
"""Handle all other exceptions globally with 500 status code."""
logging.exception("Internal Server Error")
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
# Mount the WebSocket subapp
app.mount("/auth/ws", ws.app)
@app.get("/auth/forward-auth")
async def forward_authentication(request: Request, auth=Cookie(None)):
"""A validation endpoint to use with Caddy forward_auth or Nginx auth_request."""
if auth:
with contextlib.suppress(ValueError):
s = await get_session(auth)
# If authenticated, return a success response
if s.info and s.info["type"] == "authenticated":
return Response(
status_code=204,
headers={
"x-auth-user-uuid": str(s.user_uuid),
},
)
# Serve the index.html of the authentication app if not authenticated
return FileResponse(
STATIC_DIR / "index.html",
status_code=401,
headers={"www-authenticate": "PrivateToken"},
)
# Serve static files
app.mount(
"/auth/assets", StaticFiles(directory=STATIC_DIR / "assets"), name="static assets"
)
@app.get("/auth/")
async def redirect_to_index():
"""Serve the main authentication app."""
return FileResponse(STATIC_DIR / "index.html")
@app.get("/auth/admin")
async def serve_admin():
"""Serve the admin app entry point."""
# Vite MPA builds admin as admin.html in the same outDir
admin_html = STATIC_DIR / "admin.html"
# If configured to emit admin/index.html, support that too
if not admin_html.exists():
alt = STATIC_DIR / "admin" / "index.html"
if alt.exists():
return FileResponse(alt)
return FileResponse(admin_html)
# Register API routes
register_api_routes(app)
register_reset_routes(app)