app.py
  1  """
  2  ## Authentication
  3  - To authenticate requests, the `Authorization` header must contain a valid access token (JWT which contains the user's
  4    ID and the session ID).
  5  - The access token can be obtained by logging in to an exising account (see `POST /sessions` and `POST /sessions/oauth`)
  6    or by creating an account (see `POST /users`). This access token is only valid for a short period of time
  7    (usually 5 minutes).
  8  - If the access token is expired, a new access token can be obtained by using the refresh token (see `PUT /session`)
  9    which is also returned when creating a session. This will also invalidate the refresh token and generate a new one.
 10  - If the refresh token is not used to refresh the session within a configured period of time (usually 30 days) the
 11    session expires and the user must log in again on this device.
 12  
 13  ## Special parameters
 14  - In addition to the usual user ids the `user_id` path parameter used in most endpoints also accepts the special values
 15    `me` and `self` which refer to the currently authenticated user.
 16  
 17  ## Requirements
 18  Some endpoints require one or more of the following conditions to be met:
 19  - **USER**: The user is authenticated and has a valid session.
 20  - **SELF**: The authenticated user must be the same as the affected user. Requires **USER**.
 21  - **ADMIN**: The authenticated user must be an admin. Requires **USER**.
 22  - **AUTH**: The request is authenticated using a valid API token (static/JWT).
 23  """
 24  
 25  import asyncio
 26  from typing import Awaitable, Callable, TypeVar
 27  
 28  from fastapi import FastAPI, HTTPException, Request
 29  from fastapi.exception_handlers import http_exception_handler
 30  from fastapi.middleware.cors import CORSMiddleware
 31  from fastapi.responses import Response
 32  from starlette.exceptions import HTTPException as StarletteHTTPException
 33  
 34  from .database import db, db_context
 35  from .endpoints import ROUTER, TAGS
 36  from .logger import get_logger, setup_sentry
 37  from .models import User
 38  from .models.session import clean_expired_sessions
 39  from .settings import settings
 40  from .utils.debug import check_responses
 41  from .utils.docs import add_endpoint_links_to_openapi_docs
 42  from .version import get_version
 43  
 44  
 45  T = TypeVar("T")
 46  
 47  logger = get_logger(__name__)
 48  
 49  app = FastAPI(
 50      title="FastAPI",
 51      description=__doc__,
 52      version=get_version().description,
 53      root_path=settings.root_path,
 54      root_path_in_servers=False,
 55      servers=[{"url": settings.root_path}] if settings.root_path else None,
 56      openapi_tags=TAGS,
 57  )
 58  app.include_router(ROUTER)
 59  
 60  if settings.debug:
 61      app.middleware("http")(check_responses)
 62  
 63  
 64  def setup_app() -> None:
 65      add_endpoint_links_to_openapi_docs(app.openapi())
 66  
 67      if settings.sentry_dsn:
 68          logger.debug("initializing sentry")
 69          setup_sentry(app, settings.sentry_dsn, "FastAPI", get_version().description)
 70  
 71      if settings.debug:
 72          app.add_middleware(
 73              CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"]
 74          )
 75  
 76  
 77  @app.middleware("http")
 78  async def db_session(request: Request, call_next: Callable[..., Awaitable[T]]) -> T:
 79      async with db_context():
 80          return await call_next(request)
 81  
 82  
 83  @app.exception_handler(StarletteHTTPException)
 84  async def rollback_on_exception(request: Request, exc: HTTPException) -> Response:
 85      await db.session.rollback()
 86      return await http_exception_handler(request, exc)
 87  
 88  
 89  async def clean_expired_sessions_loop() -> None:
 90      while True:
 91          try:
 92              await clean_expired_sessions()
 93          except Exception as e:
 94              logger.exception(e)
 95          await asyncio.sleep(20 * 60)
 96  
 97  
 98  @app.on_event("startup")
 99  async def on_startup() -> None:
100      setup_app()
101  
102      asyncio.create_task(clean_expired_sessions_loop())
103  
104      async with db_context():
105          await User.initialize()
106  
107  
108  @app.on_event("shutdown")
109  async def on_shutdown() -> None:
110      pass
111  
112  
113  @app.head("/status", include_in_schema=False)
114  async def status() -> None:
115      pass