> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neobank.proofof.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Single-origin Caddy edge for the dedicated demo

> The dedicated demo serves the customer app and API from one Caddy origin with real Auth0 identity and persistent Vault.

# ADR 0011: Use a single-origin Caddy edge for the dedicated demo

## Context

The dedicated demo must serve the customer application and FastAPI routes from `neobank.proofof.tech` without exposing a customer's in-progress Perflo connection to another visitor. The earlier fixed `caddy|demo` identity violated that boundary: every visitor could poll, inspect, disconnect, or replace the same provider connection. A partner-scoped production Perflo grant still does not exist, so the live adapter remains development-only even though customer identity is real.

The API also stores Vault Transit ciphertext in PostgreSQL. An ephemeral Vault key would make those credentials impossible to decrypt or revoke after a server restart.

## Decision

Make Caddy the only public service. It terminates TLS, serves the built frontend at `/`, and proxies `/v1` and health routes to FastAPI. It denies `/internal` and removes every inbound authorization and debug identity header. It does not inject an identity or send an edge credential.

Use separate Auth0 regular web applications for customer and operator login. FastAPI completes authorization code with Proof Key for Code Exchange (PKCE), validates the identity token, and creates an opaque HTTP-only `__Host-` session cookie. A second HTTP-only cookie binds each authorization transaction to the browser that started it. Customer records and Perflo connections remain keyed by the Auth0 subject. An authenticated customer session may connect or disconnect that customer's provider device without another multifactor challenge. Operator login and sensitive financial confirmations retain multifactor requirements.

Unsafe cookie-authenticated requests must carry both the exact public origin and the session's cross-site request forgery token. The dedicated demo publishes `/docs` and `/openapi.json` without authentication, but documented operations retain their route-level identity and authorization requirements. Pilot and production secure runtimes keep public documentation disabled. The local Compose ports bind only to loopback, and the dedicated overlay removes them completely.

Run Vault with persistent file storage. A persistent bootstrap volume holds the single-node unseal material, while a sidecar initializes, unseals, and verifies the derived Transit key after restarts. The application token can only encrypt, decrypt, and inspect that key.

## Consequences

The UI and API share one origin and certificate. Browser API calls need no cross-origin configuration, and the frontend contains no identity credential. A client cannot choose its neobank subject, role, or multifactor state through public headers.

Unauthenticated visitors see only the sign-in panel. Each signed-in customer can see and manage only their own Perflo connection. Connection ownership, same-origin checks, and cross-site request forgery protection remain mandatory, but a stolen active customer session could also change that customer's provider link without an MFA barrier. Live financial mutations remain unavailable through the demo adapter. The server operator must protect and back up the Vault data and bootstrap volumes together and keep every port except 80 and 443 closed.

The deployment still is not a production identity boundary. The demo adapter uses broad development-only Perflo device credentials and reviewed fixed provider origins. Production remains blocked on [scoped partner grants, hardened Vault deployment, and the production chart](get-started/production-readiness).

Unexpired device logins are preserved across page reloads instead of being overwritten. The first authenticated read rejection performs one serialized credential refresh and retries the read once. A failed refresh or a second rejection enters `reconnect_required`, where the customer must sign in to the same Perflo identity. Successful recovery preserves the binding and becomes active immediately.

The pinned Perflo evidence documents `POST /cli/token/revoke`, including its optional device selector, but does not document the response body or completeness semantics of `GET /cli/devices`. Cleanup therefore sends one revocation request for the exact stored device and resolves only after Perflo returns success. Before submission, the cleanup is durably fenced as `operator_action_required` with `device_revocation_in_progress`; a process exit or final local commit failure cannot replay the write. A definitive rejection reopens the cleanup, while a transport-uncertain revocation remains `operator_action_required` with its encrypted credential retained for investigation. A definitive credential rejection may be retried with an active credential bound to the same customer and Perflo subject. A rejected wrong-account recovery retains its encrypted access credential only for cleanup, then clears it after confirmed revocation. Selector-less cleanup never falls back to another credential because that would revoke the replacement device rather than the rejected candidate. Connection cleanup that also involves unresolved grants, financial operations, or unprovable local persistence remains `operator_action_required`.

## Rejected alternatives

* Publish FastAPI on port 8000: it bypasses Caddy-managed TLS, header stripping, and the private-service boundary.
* Put the API on a separate host: it adds a cross-origin cookie and CSRF boundary without demo value.
* Use ephemeral Vault dev mode: a restart strands live Perflo device credentials.
* Keep the fixed Caddy identity: it exposes one customer's provider connection lifecycle to every visitor.
* Put an edge credential in the frontend: browser users could extract it and forge debug principals.
* Use Caddy Basic Auth or an application password: one credential would still collapse every visitor into the same customer, while per-user credentials would recreate an identity provider.
* Reassign the existing `caddy|demo` connection to the first Auth0 user: that would transfer provider credentials across identities without proof of ownership.
