Some checks failed
Architecture Governance / architecture (push) Has been cancelled
52 lines
2.2 KiB
Markdown
52 lines
2.2 KiB
Markdown
---
|
|
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.
|