Files
dsh/server/dsh_sync/main.py
T

56 lines
1.7 KiB
Python

"""FastAPI application assembly.
Endpoints live in `dsh_sync.routers.*`, one module per resource. This file only
wires configuration, middleware, and routers together.
"""
from __future__ import annotations
from fastapi import FastAPI, Request
from .auth import ensure_bootstrap_admin
from .config import Config
from .db import init_db
from .ratelimit import SlidingWindow
from .routers import audit, backup, connect, pages, plugins, settings, tokens, users
from .util import now_iso
# Security headers for every response. The app is served over plain HTTP on a
# LAN, so HSTS is deliberately absent; `camera=(self)` is required because the
# PWA's in-app scanner calls getUserMedia on the same origin.
SECURITY_HEADERS = {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Referrer-Policy": "no-referrer",
"Permissions-Policy": "geolocation=(), microphone=(), camera=(self)",
}
def create_app(cfg: Config | None = None) -> FastAPI:
cfg = cfg or Config.from_env()
cfg.ensure_dirs()
init_db(cfg)
ensure_bootstrap_admin(cfg)
app = FastAPI(title="dsh-sync", version="0.1.0")
app.state.cfg = cfg
app.state.invite_limiter = SlidingWindow(limit=10, window_seconds=60)
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
for k, v in SECURITY_HEADERS.items():
response.headers.setdefault(k, v)
return response
@app.get("/healthz")
def healthz():
return {"status": "ok", "time": now_iso()}
for module in (pages, connect, tokens, users, settings, plugins, audit, backup):
app.include_router(module.router)
return app
app = create_app()