fix(api): derive host from storefront domain
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

Every storefront, including nested subdomains, uses its matching api.<hostname> endpoint.
This commit is contained in:
2026-08-20 14:31:22 +04:00
parent f4ea4c7af8
commit 640360d63c
15 changed files with 88 additions and 120 deletions

View File

@@ -0,0 +1,52 @@
---
id: ADR-0004
title: Derive each API host from the complete storefront host
status: active
date: 2026-08-20
supersedes: []
tags: [architecture, multi-tenant, api, routing, dns]
---
# ADR-0004: Derive each API host from the complete storefront host
## Context
One production bundle serves root domains and arbitrary storefront subdomains.
The old bundle embedded `api.dexarmarket.ru`, while an earlier correction used
a same-origin `/backend` gateway. Neither expresses the required domain rule:
each storefront has a corresponding API hostname derived from its full host.
Examples:
- `example.com` uses `api.example.com`.
- `store1.example.com` uses `api.store1.example.com`.
Bootstrap, auth, legacy endpoints, and versioned endpoints must not use
different base-host selection rules.
## Decision
At runtime the frontend prefixes the complete browser hostname with `api.` and
keeps the browser protocol: `{protocol}//api.{hostname}`.
- Bootstrap loads from `https://api.{hostname}/bootstrap`.
- Auth receives the same derived base through `AUTH_API_URL`.
- Legacy endpoints append their existing paths to that base.
- Versioned `/api/...` endpoints retain the `/api` prefix.
- Localhost and loopback continue to use the local `/api` development proxy.
- An explicit `tenantApiBaseUrls` entry may override the convention for an
exceptional host, without changing the shared bundle.
The complete hostname is preserved. In particular, `www.example.com` maps to
`api.www.example.com`; no label is stripped or interpreted by the frontend.
## Consequences
One artifact works on root domains and nested storefront subdomains without a
tenant allowlist or per-domain build. Every API hostname must have DNS, TLS, a
working reverse proxy, and CORS configured for its corresponding storefront.
A wildcard such as `*.example.com` does not cover the multi-label hostname
`api.store1.example.com`; nested API names need explicit certificates/DNS or a
certificate and routing strategy that covers that depth. Backend tenant lookup
must recognize `api.<storefront-host>` as the API alias of `<storefront-host>`.

View File

@@ -1,51 +0,0 @@
---
id: ADR-0004
title: Route every tenant API through a same-origin gateway
status: active
date: 2026-08-20
supersedes: []
tags: [architecture, multi-tenant, api, routing, nginx]
---
# ADR-0004: Route every tenant API through a same-origin gateway
## Context
The production bundle embedded `api.dexarmarket.ru` and constructed unknown tenant
URLs as `https://{tenant}.api.dexarmarket.ru:445`. Bootstrap used a different
route (`/bootstrap` on the storefront origin), while auth had its own fixed API
base. A custom domain could therefore use three different backend origins.
Directly calling the backend port is not a safe fallback: production returned
`403` for both a bootstrap request carrying `Origin: https://gorbushka.market`
and the corresponding CORS preflight. Meanwhile an unmatched `/bootstrap` on
the storefront nginx server fell through to `index.html`, producing a misleading
HTTP 200 with HTML instead of bootstrap JSON.
## Decision
Every tenant frontend uses one API base derived at runtime from the browser
origin: `{origin}/backend`.
- Bootstrap loads from `{origin}/backend/bootstrap`.
- Auth receives the same base through the `AUTH_API_URL` provider.
- Legacy endpoints append their existing paths to the same base.
- Versioned `/api/...` endpoints retain the `/api` prefix when routed.
- nginx owns `/backend/`, removes that prefix when proxying, and forwards the
original `Host`, `X-Forwarded-For`, and `X-Forwarded-Proto` headers upstream.
The frontend contains no tenant/domain allowlist and no production backend
hostname. Tenant selection remains a server-side responsibility based on the
verified forwarded host.
## Consequences
Browser traffic is same-origin, so custom domains do not require per-tenant CORS
configuration and one bundle works for every attached domain. Bootstrap, auth,
legacy routes, and versioned routes cannot silently drift to different hosts.
Every nginx tenant/catch-all configuration must include the `/backend/` gateway.
Deploy verification must check that `/backend/bootstrap` returns JSON rather
than accepting a generic HTTP 200 from the SPA fallback. The upstream must still
reject unknown hosts; the gateway preserves `Host` but does not authenticate a
tenant by itself.