fix(api): route tenants through origin gateway
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

This commit is contained in:
2026-08-20 14:23:12 +04:00
parent bbf12cad33
commit f4ea4c7af8
18 changed files with 241 additions and 69 deletions

View File

@@ -0,0 +1,51 @@
---
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.

View File

@@ -11,3 +11,4 @@
{"id":"PV-20260818T104300Z-b3c4","subject":"RoutingContext","predicate":"is-required-on","object":"CheckoutSession, PaymentIntent, Payment, Refund and ReconciliationRecord; frozen at checkout-session creation and immutable thereafter, so a payment is always attributable to exactly one payment point","src":["docs/backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md","docs/backend/PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md"],"status":"active","kind":"constraint","updated_at":"2026-08-18T10:43:00Z","confidence":"high","tags":["payments","reconciliation","contract"]}
{"id":"PV-20260818T104400Z-d9e2","subject":"partner-api-credentials","predicate":"are-scoped-by","object":"a single node whose subtree defines authority; we hold only the partner-generated public key, rotation runs on a bounded overlap window and revocation is immediate and irreversible","src":["docs/backend/PARTNER-PROVISIONING-API-CONTRACT.md","docs/backend/TRACK-S-SECURITY-RBAC-CONTRACT.md"],"status":"active","kind":"decision","updated_at":"2026-08-18T10:44:00Z","confidence":"high","tags":["security","credentials","partner"]}
{"id":"PV-20260818T104500Z-a6f7","subject":"checkout-payment-methods","predicate":"already-support","object":"both qr and card end to end in src/app/pages/cart/cart.component.ts (separate create paths and separate status pollers); card is not an outstanding gap","src":["src/app/pages/cart/cart.component.ts","src/app/services/api.service.ts"],"status":"active","kind":"implemented","updated_at":"2026-08-18T10:45:00Z","confidence":"high","tags":["payments","frontend"]}
{"id":"PV-20260820T095500Z-b17e","subject":"tenant-api-routing","predicate":"is-decided-to-use","object":"one same-origin {origin}/backend gateway for bootstrap, auth, legacy endpoints, and versioned /api endpoints; nginx strips /backend and preserves the original Host for server-side tenant resolution","src":["docs/context/adrs/ADR-0004-route-every-tenant-api-through-a-same-origin-gateway.md","src/app/core/config/api-config.service.ts","scripts/deploy/server-setup.sh"],"status":"active","kind":"decision","updated_at":"2026-08-20T09:55:00Z","confidence":"high","tags":["architecture","multi-tenant","api","routing","nginx"]}