fix(deploy): ADMIN_ORIGIN must list the marketing site too

/start now posts account signups straight to admin, so the browser sends
a cross-origin preflight from vantage.hostxtra.co.uk. With only the
console origin in ADMIN_ORIGIN it was refused.

The failure mode is worth naming: cors() omits Access-Control-Allow-Origin
but still answers the preflight 204, so the browser blocks the request and
admin logs nothing. It looks like the endpoint is down when curl says it
is fine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrhid6
2026-07-26 14:02:25 +01:00
co-authored by Claude Opus 5
parent 104f21d0a9
commit b209fed4a7
2 changed files with 10 additions and 2 deletions
+4 -2
View File
@@ -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. |
---
+6
View File
@@ -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:-}