Files
frigate/frigate/api/fastapi_app.py
T

189 lines
6.4 KiB
Python
Raw Normal View History

2026-05-27 10:19:11 -05:00
import asyncio
2024-09-24 14:05:30 +01:00
import logging
2025-08-19 13:19:31 -06:00
import re
2024-09-24 14:05:30 +01:00
from typing import Optional
from fastapi import Depends, FastAPI, Request
2024-09-24 14:05:30 +01:00
from fastapi.responses import JSONResponse
2025-08-19 13:19:31 -06:00
from joserfc.jwk import OctKey
2024-09-24 14:05:30 +01:00
from playhouse.sqliteq import SqliteQueueDatabase
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from starlette_context import middleware, plugins
from starlette_context.plugins import Plugin
from frigate.api import app as main_app
2024-10-22 16:05:48 -06:00
from frigate.api import (
auth,
camera,
chat,
2024-10-22 16:05:48 -06:00
classification,
2026-03-04 10:07:34 -06:00
debug_replay,
2024-10-22 16:05:48 -06:00
event,
export,
media,
motion_search,
2024-10-22 16:05:48 -06:00
notification,
preview,
record,
2024-10-22 16:05:48 -06:00
review,
)
from frigate.api.auth import get_jwt_secret, limiter, require_admin_by_default
2026-03-19 09:47:57 -05:00
from frigate.comms.dispatcher import Dispatcher
from frigate.comms.event_metadata_updater import (
EventMetadataPublisher,
)
2024-09-24 14:05:30 +01:00
from frigate.config import FrigateConfig
from frigate.config.camera.updater import CameraConfigUpdatePublisher
2026-03-19 09:47:57 -05:00
from frigate.config.profile_manager import ProfileManager
2026-05-27 10:19:11 -05:00
from frigate.debug_replay import DebugReplayManager, debug_replay_auto_stop_watchdog
2024-09-24 14:05:30 +01:00
from frigate.embeddings import EmbeddingsContext
from frigate.genai import GenAIClientManager
2024-09-24 14:05:30 +01:00
from frigate.ptz.onvif import OnvifController
from frigate.stats.emitter import StatsEmitter
from frigate.storage import StorageMaintainer
logger = logging.getLogger(__name__)
2025-01-30 10:27:38 -07:00
def check_csrf(request: Request) -> bool:
2024-09-24 14:05:30 +01:00
if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]:
2025-01-30 10:27:38 -07:00
return True
2024-09-24 14:05:30 +01:00
if "origin" in request.headers and "x-csrf-token" not in request.headers:
2025-01-30 10:27:38 -07:00
return False
return True
2024-09-24 14:05:30 +01:00
# Used to retrieve the remote-user header: https://starlette-context.readthedocs.io/en/latest/plugins.html#easy-mode
class RemoteUserPlugin(Plugin):
key = "Remote-User"
def create_fastapi_app(
frigate_config: FrigateConfig,
database: SqliteQueueDatabase,
embeddings: Optional[EmbeddingsContext],
detected_frames_processor,
storage_maintainer: StorageMaintainer,
onvif: OnvifController,
stats_emitter: StatsEmitter,
event_metadata_updater: EventMetadataPublisher,
config_publisher: CameraConfigUpdatePublisher,
2026-03-04 10:07:34 -06:00
replay_manager: DebugReplayManager,
2026-03-19 09:47:57 -05:00
dispatcher: Optional[Dispatcher] = None,
profile_manager: Optional[ProfileManager] = None,
enforce_default_admin: bool = True,
2024-09-24 14:05:30 +01:00
):
logger.info("Starting FastAPI app")
app = FastAPI(
debug=False,
swagger_ui_parameters={"apisSorter": "alpha", "operationsSorter": "alpha"},
dependencies=[Depends(require_admin_by_default())]
if enforce_default_admin
else [],
2024-09-24 14:05:30 +01:00
)
# update the request_address with the x-forwarded-for header from nginx
# https://starlette-context.readthedocs.io/en/latest/plugins.html#forwarded-for
app.add_middleware(
middleware.ContextMiddleware,
plugins=(plugins.ForwardedForPlugin(),),
)
# Middleware to connect to DB before and close connection after request
# https://github.com/fastapi/full-stack-fastapi-template/issues/224#issuecomment-737423886
# https://fastapi.tiangolo.com/tutorial/middleware/#before-and-after-the-response
@app.middleware("http")
async def frigate_middleware(request: Request, call_next):
# Before request
2025-01-30 10:27:38 -07:00
if not check_csrf(request):
return JSONResponse(
content={"success": False, "message": "Missing CSRF header"},
status_code=401,
)
2024-09-24 14:05:30 +01:00
if database.is_closed():
database.connect()
response = await call_next(request)
# After request https://stackoverflow.com/a/75487519
if not database.is_closed():
database.close()
return response
@app.on_event("startup")
async def startup():
logger.info("FastAPI started")
2026-05-27 10:19:11 -05:00
asyncio.create_task(
debug_replay_auto_stop_watchdog(
replay_manager, frigate_config, config_publisher
)
)
2024-09-24 14:05:30 +01:00
# Rate limiter (used for login endpoint)
2024-12-10 07:42:55 -06:00
if frigate_config.auth.failed_login_rate_limit is None:
limiter.enabled = False
else:
auth.rateLimiter.set_limit(frigate_config.auth.failed_login_rate_limit)
2024-09-24 14:05:30 +01:00
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)
# Routes
# Order of include_router matters: https://fastapi.tiangolo.com/tutorial/path-params/#order-matters
app.include_router(auth.router)
app.include_router(camera.router)
app.include_router(chat.router)
2024-10-22 16:05:48 -06:00
app.include_router(classification.router)
2024-09-24 14:05:30 +01:00
app.include_router(review.router)
app.include_router(main_app.router)
app.include_router(preview.router)
app.include_router(notification.router)
app.include_router(export.router)
app.include_router(event.router)
app.include_router(media.router)
app.include_router(motion_search.router)
app.include_router(record.router)
2026-03-04 10:07:34 -06:00
app.include_router(debug_replay.router)
2024-09-24 14:05:30 +01:00
# App Properties
app.frigate_config = frigate_config
app.genai_manager = GenAIClientManager(frigate_config)
2024-09-24 14:05:30 +01:00
app.embeddings = embeddings
app.detected_frames_processor = detected_frames_processor
app.storage_maintainer = storage_maintainer
app.camera_error_image = None
app.onvif = onvif
app.stats_emitter = stats_emitter
app.event_metadata_updater = event_metadata_updater
app.config_publisher = config_publisher
2026-03-04 10:07:34 -06:00
app.replay_manager = replay_manager
2026-03-19 09:47:57 -05:00
app.dispatcher = dispatcher
app.profile_manager = profile_manager
2025-08-19 13:19:31 -06:00
if frigate_config.auth.enabled:
secret = get_jwt_secret()
key_bytes = None
if isinstance(secret, str):
# If the secret looks like hex (e.g., generated by secrets.token_hex), use raw bytes
if len(secret) % 2 == 0 and re.fullmatch(r"[0-9a-fA-F]+", secret or ""):
try:
key_bytes = bytes.fromhex(secret)
except ValueError:
key_bytes = secret.encode("utf-8")
else:
key_bytes = secret.encode("utf-8")
elif isinstance(secret, (bytes, bytearray)):
key_bytes = bytes(secret)
else:
key_bytes = str(secret).encode("utf-8")
app.jwt_token = OctKey.import_key(key_bytes)
else:
app.jwt_token = None
2024-09-24 14:05:30 +01:00
return app