00bfe8bfca
Pluggable AuthProvider interface with a local (email+password) implementation, and a Mailer interface (ConsoleMailer for dev, SMTPMailer for operators). The auth service owns registration, login, opaque session issuance, email verification, and password reset (which revokes prior sessions). Endpoints under /api/v1/auth; sessions are returned as a Bearer token and set as an HttpOnly cookie. Replaces the temporary X-User-Id shim: get_current_user now resolves a real session (Bearer or cookie). The open user-bootstrap endpoint is gone (registration replaces it). App logging is configured so the ConsoleMailer's verification/reset links are visible to self-hosters. Verified end-to-end on the deploy target, including the email-verification flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Paul <justin@jpaul.me>
62 lines
2.1 KiB
Python
62 lines
2.1 KiB
Python
"""FastAPI application entrypoint.
|
|
|
|
Thin by design: wire settings, routers, and error handling, and expose the
|
|
OpenAPI contract. All domain logic lives in the service layer; the privacy
|
|
engine is the single enforcement point for reads.
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.api.health import router as health_router
|
|
from app.api.v1 import api_router
|
|
from app.core.config import get_settings
|
|
from app.services.exceptions import Conflict, Forbidden, NotFound
|
|
|
|
|
|
def _configure_logging() -> None:
|
|
"""Emit the app's own ``provenance.*`` logs at INFO to stdout (uvicorn only
|
|
configures its own loggers). The ConsoleMailer relies on this so self-hosters
|
|
can read verification/reset links from the logs."""
|
|
app_logger = logging.getLogger("provenance")
|
|
app_logger.setLevel(logging.INFO)
|
|
if not app_logger.handlers:
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(logging.Formatter("%(levelname)s [%(name)s] %(message)s"))
|
|
app_logger.addHandler(handler)
|
|
app_logger.propagate = False
|
|
|
|
|
|
def _register_error_handlers(app: FastAPI) -> None:
|
|
@app.exception_handler(NotFound)
|
|
async def _not_found(request: Request, exc: NotFound) -> JSONResponse:
|
|
return JSONResponse(status_code=404, content={"detail": str(exc) or "not found"})
|
|
|
|
@app.exception_handler(Forbidden)
|
|
async def _forbidden(request: Request, exc: Forbidden) -> JSONResponse:
|
|
return JSONResponse(status_code=403, content={"detail": str(exc) or "forbidden"})
|
|
|
|
@app.exception_handler(Conflict)
|
|
async def _conflict(request: Request, exc: Conflict) -> JSONResponse:
|
|
return JSONResponse(status_code=409, content={"detail": str(exc) or "conflict"})
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
_configure_logging()
|
|
settings = get_settings()
|
|
app = FastAPI(
|
|
title=settings.app_name,
|
|
version=settings.version,
|
|
description="Provenance API — family and land provenance.",
|
|
)
|
|
app.include_router(health_router)
|
|
app.include_router(api_router)
|
|
_register_error_handlers(app)
|
|
return app
|
|
|
|
|
|
app = create_app()
|