diff --git a/claude.md b/claude.md index 98dbd88..e551013 100644 --- a/claude.md +++ b/claude.md @@ -147,7 +147,9 @@ Agents report CPU/memory/swap/partitions/kernel — metrics every 30s, full stat `site/` is a separate Next.js app built exactly like `web/` — `output: "standalone"`, run by Node in a `node:26-alpine` image, listening on `3000` and published as `3003`. The contact form posts to `sitesvc`; account signup posts to `admin` (`NEXT_PUBLIC_ADMIN_API_URL`), which creates an HQ account, not an org — the control plane is not touched until the customer later creates a cloud instance from the portal. -`adminsite/` is built the same way and published as `3004`, served at **`vantage-hq.hostxtra.co.uk`** — deliberately *outside* `*.vantage.hostxtra.co.uk`, because that namespace is per-tenant instance subdomains and `APP_ROOT_LABEL` resolves an org from the label before `vantage`. It shares `site/`'s design tokens verbatim (see Frontend below) and, unlike `web/`, does **not** proxy through a Next rewrite: the browser calls `admin` directly, so `ADMIN_API_URL` must be browser-reachable and listed in admin's `ADMIN_ORIGIN`. Authenticated requests work cross-origin only because both hosts share the registrable domain `hostxtra.co.uk`, which keeps `admin_session`'s `SameSite=Lax` cookie in play. +`adminsite/` is built the same way and published as `3004`, served at **`vantage-hq.hostxtra.co.uk`** — deliberately *outside* `*.vantage.hostxtra.co.uk`, because that namespace is per-tenant instance subdomains and `APP_ROOT_LABEL` resolves an org from the label before `vantage`. It shares `site/`'s design tokens verbatim (see Frontend below) and, unlike `web/`, does **not** proxy through a Next rewrite: the browser calls `admin` directly, so `ADMIN_API_URL` must be browser-reachable. Authenticated requests work cross-origin only because both hosts share the registrable domain `hostxtra.co.uk`, which keeps `admin_session`'s `SameSite=Lax` cookie in play. + +**`ADMIN_ORIGIN` must list every browser origin that calls admin — currently two**: `https://vantage-hq.hostxtra.co.uk` for the console, and `https://vantage.hostxtra.co.uk` because the marketing site's `/start` form posts account signups to admin directly. It is comma-separated. A missing origin does not produce a 403: `cors()` simply omits the `Access-Control-Allow-Origin` header and still answers the preflight `204`, so the browser blocks the request and **admin logs nothing at all**. Symptom is a CORS preflight failure on an endpoint that works fine under curl. `sitesvc/` (port `8082`) now owns only the contact flow: @@ -508,7 +510,7 @@ git push origin main # server + web deploy | `API_URL` | Variable | baked into the `web` image at build time | | `SITE_API_URL` | Variable | **browser-reachable** sitesvc URL, baked into the `site` image. Required — if empty, both forms report "not connected" and submit nowhere. Must also be in sitesvc's `SITE_ORIGIN`. | | `SITE_CONTACT_EMAIL` | Variable | optional; address shown when a form is misconfigured | -| `ADMIN_API_URL` | Variable | **browser-reachable** admin URL, baked into the `adminsite` image. Same footgun as `SITE_API_URL`: wrong here and every request fails at runtime with the not-connected panel. Must also be in admin's `ADMIN_ORIGIN`. | +| `ADMIN_API_URL` | Variable | **browser-reachable** admin URL, baked into **both** the `adminsite` and `site` images — `site/start` posts account signups straight to admin. Same footgun as `SITE_API_URL`: wrong here and every request fails at runtime with the not-connected panel. | | `ADMIN_ENV` | Variable | `production` or `sandbox`; drives the persistent environment badge. Anything but `sandbox` reads as production. | --- diff --git a/deploy/docker-compose.site.yml b/deploy/docker-compose.site.yml index 61b5a91..a73b3c1 100644 --- a/deploy/docker-compose.site.yml +++ b/deploy/docker-compose.site.yml @@ -42,6 +42,12 @@ services: REDIS_PASSWORD: ${REDIS_PASSWORD:-} LICENSE_SIGNING_KEY: ${LICENSE_SIGNING_KEY:-} PUBLIC_URL: ${ADMIN_PUBLIC_URL:-} + # Comma-separated, and it needs BOTH browser origins that call admin: + # vantage-hq (the console) and the marketing site, whose /start form + # posts account signups here. An origin missing from this list is not + # a 403 — admin omits the CORS header entirely and the browser blocks + # the preflight, so the form fails with nothing in admin's logs. + # ADMIN_ORIGIN="https://vantage-hq.hostxtra.co.uk,https://vantage.hostxtra.co.uk" ADMIN_ORIGIN: ${ADMIN_ORIGIN:-} TRUST_PROXY: ${TRUST_PROXY:-true} SMTP_HOST: ${SMTP_HOST:-}