"""The DTF Portal/API service: assembly only. Configuration and shared helpers are in local.runtime; every route lives in a router under local.api. This module creates the application, applies the cross-cutting middleware, and includes them. """ from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.responses import JSONResponse from starlette.middleware.trustedhost import TrustedHostMiddleware from .core import db from .core.auth import audit, client_ip from .runtime import ALLOWED_HOSTS, ALLOWED_ORIGINS, storage from .api import artwork, customer, health, operator, orders, payments, quotes, uploads @asynccontextmanager async def lifespan(app): with db.connect() as c: c.execute('SELECT 1 FROM dtf_local.operator_sessions LIMIT 1') storage.health() yield app = FastAPI(title='DTF Portal/API', lifespan=lifespan, docs_url=None, redoc_url=None) app.add_middleware(TrustedHostMiddleware, allowed_hosts=ALLOWED_HOSTS) @app.middleware('http') async def safe_headers(request, call_next): if request.method not in ('GET','HEAD','OPTIONS'): origin = request.headers.get('origin') if request.headers.get('sec-fetch-site') == 'cross-site' or (origin and origin not in ALLOWED_ORIGINS): audit('cross_origin_rejected', ip=client_ip(request)) return JSONResponse({'detail':'Cross-origin request rejected'}, status_code=403) response = await call_next(request) if response.status_code in (401,403,429) or response.status_code>=500: audit('http_security_event', method=request.method, status=response.status_code, ip=client_ip(request)) response.headers['Cache-Control'] = 'no-store' response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['Referrer-Policy'] = 'no-referrer' return response # Order is not significant: no two routers declare the same path. for module in (health, uploads, quotes, orders, payments, operator, customer, artwork): app.include_router(module.router)