app.web¶
Serve the built SPA and its runtime configuration from the API process.
Mounted by app.main.create_app() only when
ADRIANO_STATIC_DIR is set (the single container image sets it to
the built dist/). Routing, with the API keeping precedence:
every API route (
/api/...,/healthz,/openapi.json,/api/docs,/auth/dev/...) is matched first and behaves as without the SPA;GET /config.jsis rendered once fromSettings(module-runtime-config-spa):window.__APP_CONFIG__ = {...};with exactly the keys offrontend/src/config.ts’sRuntimeConfig. It never carries a dev-auth flag;/assets/*(content-hashed bundle files) are served with a one-yearimmutablecache;any other
GET/HEADthat matches no route serves an existing top-level file of the SPA directory (e.g.favicon.svg) or elseindex.htmlwithno-cache, so history-mode deep links and/auth/callbackwork. Unknown paths under the API prefixes (and missing assets) stay plain JSON 404s.
The fallback is a 404 exception handler rather than a catch-all
route, so it never shadows a route and never turns an API 405 into
something else.
SecurityHeadersMiddleware adds the headers nginx used to set
to every response (see content_security_policy()).
Public API: mount_spa(), render_config_js(),
content_security_policy(), SecurityHeadersMiddleware.
- app.web.NO_FALLBACK_PREFIXES = ('/api', '/auth/dev', '/healthz', '/openapi.json', '/assets', '/config.js')¶
Path prefixes that never fall back to
index.html: an unknown path below them is a JSON 404. A prefix matches itself and anything below<prefix>/.
- app.web.DOCS_PATHS = ('/api/docs', '/redoc')¶
Interactive API documentation pages (FastAPI’s Swagger UI and ReDoc), which load their scripts and styles from a CDN and use inline scripts.
- app.web.DOCS_CSP = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: https://fastapi.tiangolo.com https://cdn.redoc.ly; worker-src 'self' blob:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'"¶
CSP for the documentation pages: the SPA policy would block the CDN assets and inline scripts of Swagger UI / ReDoc. Still no framing, no foreign connections, no base or form targets.
- app.web.content_security_policy(idp_origin)¶
Build the SPA’s Content-Security-Policy.
connect-src: oidc-client-ts fetches the IdP discovery document, token and userinfo endpoints from the browser.frame-src: its silent renew loads the IdP authorize endpoint in a hidden iframe. Vuetify sets theme CSS variables through inlinestyleattributes, which needsstyle-src 'unsafe-inline'(there is no nonce mechanism for style attributes).
- app.web.render_config_js(settings)¶
Render
/config.jsfrom the settings.The object is JSON-encoded (no template substitution, so no injection), and every
<is escaped so no value can close a<script>element even if the file is ever inlined. Unset values are empty strings, whichfrontend/src/config.tstreats as unset.apiBaseUrlis always"": the API is same-origin.
- class app.web.SecurityHeadersMiddleware(app, csp)¶
Bases:
objectAdd the security headers to every HTTP response.
Applied to API responses as well: a CSP on JSON is harmless and protects against a response ever being rendered as a document. The documentation pages (
DOCS_PATHS) getDOCS_CSPinstead. A header the response already sets is left alone.
- app.web.mount_spa(app, settings)¶
Serve the SPA,
/config.jsand the security headers.Call after every API router is registered.
- Parameters:
app (
FastAPI) – The application under construction.settings (
Settings) – Application settings;static_dirmust be set.
- Raises:
ConfigurationError – If
static_diris unset or holds noindex.html.- Return type: