This commit is contained in:
103
docs/backend-platform/architecture.md
Normal file
103
docs/backend-platform/architecture.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Backend Platform Architecture
|
||||
|
||||
## 1. System Overview
|
||||
This platform is a domain-based multi-tenant SaaS marketplace.
|
||||
|
||||
- Frontend (Angular) is configuration-driven.
|
||||
- Backend (Node.js) is tenant-aware and resolves tenant by request domain.
|
||||
- UI composition is delivered by `GET /bootstrap` from the backend CONFIG DOMAIN.
|
||||
- Business operations are delivered by existing BUSINESS DOMAIN APIs (`/auth`, `/items`, `/categories`, `/orders`, `/cart`, `/payments`).
|
||||
|
||||
Core architectural rule:
|
||||
- No projectName-based behavior.
|
||||
- No environment-based business branching.
|
||||
- Runtime behavior is tenant-driven by domain + tenant configuration.
|
||||
|
||||
## 2. Tenant Resolution by Domain
|
||||
Tenant identity is resolved from the incoming host:
|
||||
- `shop-a.example.com` -> tenant A
|
||||
- `shop-b.example.com` -> tenant B
|
||||
|
||||
Resolution output is attached to request context and used by:
|
||||
- Config domain (`/bootstrap`, `/pages/:slug`)
|
||||
- Business APIs (data isolation and policy checks)
|
||||
|
||||
## 3. CONFIG DOMAIN vs BUSINESS DOMAIN
|
||||
|
||||
### CONFIG DOMAIN
|
||||
Purpose: return public runtime configuration for frontend composition.
|
||||
Includes:
|
||||
- tenant metadata (public)
|
||||
- theme
|
||||
- layout mode
|
||||
- widget registry metadata
|
||||
- page/section/widget structure
|
||||
- footer/static pages metadata
|
||||
- supported locales/currencies
|
||||
- endpoint mapping (public)
|
||||
|
||||
### BUSINESS DOMAIN
|
||||
Purpose: transactional and catalog operations.
|
||||
Includes:
|
||||
- authentication/session
|
||||
- products/items
|
||||
- categories
|
||||
- cart
|
||||
- orders
|
||||
- payments
|
||||
|
||||
Boundary rule:
|
||||
- BUSINESS APIs do not return UI layout/theme/widget composition.
|
||||
- CONFIG APIs do not return transactional business state.
|
||||
|
||||
## 4. Bootstrap API Role
|
||||
`GET /bootstrap` initializes frontend runtime.
|
||||
|
||||
Backend responsibilities:
|
||||
1. Resolve tenant from domain.
|
||||
2. Load tenant config aggregate.
|
||||
3. Return versioned, public bootstrap payload.
|
||||
4. Never leak secrets in bootstrap.
|
||||
|
||||
Frontend responsibilities:
|
||||
1. Load bootstrap at startup.
|
||||
2. Render based on config only.
|
||||
3. Use business APIs only for domain data/actions.
|
||||
|
||||
## 5. Existing API Domains (Unchanged)
|
||||
- `/auth`
|
||||
- `/items`
|
||||
- `/categories`
|
||||
- `/orders`
|
||||
- `/cart`
|
||||
- `/payments`
|
||||
|
||||
These APIs remain authoritative for business workflows and must not be rewritten for UI composition.
|
||||
|
||||
## 6. Data Flow Diagram (Text)
|
||||
```text
|
||||
Browser Request
|
||||
-> Edge/Ingress (Host preserved)
|
||||
-> Node.js API Gateway
|
||||
-> Tenant Resolver Middleware (host -> tenant)
|
||||
-> Request Context Enrichment (tenantId, locale, policy)
|
||||
-> Route Dispatch
|
||||
-> /bootstrap (CONFIG DOMAIN) -> Config Services -> Response JSON
|
||||
-> /items|/orders|... (BUSINESS DOMAIN) -> Business Services -> Response JSON
|
||||
<- Tenant-scoped response
|
||||
```
|
||||
|
||||
## 7. Request Lifecycle (Browser -> Backend -> Tenant -> Response)
|
||||
1. Browser sends request with `Host` header.
|
||||
2. Backend middleware resolves tenant by domain.
|
||||
3. Backend validates tenant status (active, allowed, mapped).
|
||||
4. Tenant context is attached to request (`req.ctx.tenant`).
|
||||
5. Route handler executes with tenant-scoped repositories/services.
|
||||
6. Response is returned with tenant-scoped data.
|
||||
|
||||
## 8. Scalability Notes (10–100+ Tenants)
|
||||
- Keep tenant config in low-latency cache with invalidation.
|
||||
- Use stateless API instances; tenant context is per request.
|
||||
- Enforce strict tenant filters at repository/query layer.
|
||||
- Monitor by tenant dimensions (latency, errors, saturation).
|
||||
- Apply rate limits and abuse controls per tenant/domain.
|
||||
151
docs/backend-platform/bootstrap-api-spec.md
Normal file
151
docs/backend-platform/bootstrap-api-spec.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Bootstrap API Specification
|
||||
|
||||
## 1. Endpoint
|
||||
- Method: `GET`
|
||||
- Path: `/bootstrap`
|
||||
- Auth: public (or optional lightweight token), tenant-scoped by domain
|
||||
|
||||
## 2. Domain-Based Request Flow
|
||||
1. Receive request with host.
|
||||
2. Resolve tenant by host.
|
||||
3. Load tenant config aggregate from CONFIG DOMAIN.
|
||||
4. Build versioned bootstrap payload.
|
||||
5. Return public config JSON.
|
||||
|
||||
Failure responses:
|
||||
- `404` unknown tenant domain
|
||||
- `403` tenant inactive/suspended
|
||||
- `500` config assembly failure
|
||||
|
||||
## 3. Production-Like Sample Response
|
||||
```json
|
||||
{
|
||||
"schemaVersion": "2.1.0",
|
||||
"generatedAt": "2026-07-05T10:30:00Z",
|
||||
"tenant": {
|
||||
"id": "a95c2f1b-58c1-4d8b-b35b-82e5bdf14321",
|
||||
"slug": "alpha-market",
|
||||
"code": "ALPHA",
|
||||
"host": "shop.alpha.example.com",
|
||||
"name": "Alpha Marketplace",
|
||||
"defaultLocale": "en",
|
||||
"supportedLocales": ["en", "ru", "hy"],
|
||||
"defaultCurrency": "USD",
|
||||
"supportedCurrencies": ["USD", "EUR", "AMD"],
|
||||
"timezone": "UTC"
|
||||
},
|
||||
"theme": {
|
||||
"themeId": "alpha-light",
|
||||
"mode": "light",
|
||||
"palette": {
|
||||
"primary": "#2F6F6D",
|
||||
"secondary": "#9FB8B6",
|
||||
"accent": "#B5D7D4",
|
||||
"textPrimary": "#1E3C38",
|
||||
"textSecondary": "#5E7471",
|
||||
"backgroundPrimary": "#FFFFFF",
|
||||
"backgroundSecondary": "#F6F8F8",
|
||||
"border": "#D7E0DF"
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"type": "sidebar-left",
|
||||
"options": {
|
||||
"sidebarSticky": true,
|
||||
"heroEnabled": true
|
||||
}
|
||||
},
|
||||
"widgetRegistry": {
|
||||
"manifestUrl": "/config/widgets/manifest.json"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"id": "page-home",
|
||||
"key": "home",
|
||||
"route": { "path": "/", "exact": true },
|
||||
"layout": { "type": "carousel-home" },
|
||||
"sections": [
|
||||
{
|
||||
"id": "sec-hero",
|
||||
"type": "hero",
|
||||
"order": 1,
|
||||
"widgets": [
|
||||
{
|
||||
"id": "w-hero-main",
|
||||
"type": "hero",
|
||||
"version": "1.0.0",
|
||||
"order": 1,
|
||||
"padding": "0.5rem 0",
|
||||
"visibility": { "desktop": true, "tablet": true, "mobile": true },
|
||||
"props": { "title": "Welcome", "subtitle": "B2B Catalog" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"paymentIcons": [
|
||||
{ "src": "/assets/payments/visa.svg", "alt": "Visa", "width": 40, "height": 28 },
|
||||
{ "src": "/assets/payments/mastercard.svg", "alt": "Mastercard", "width": 40, "height": 28 }
|
||||
],
|
||||
"copyrightText": {
|
||||
"en": "© 2026 Alpha Marketplace. All rights reserved.",
|
||||
"ru": "© 2026 Alpha Marketplace. Все права защищены.",
|
||||
"hy": "© 2026 Alpha Marketplace. Բոլոր իրավունքները պաշտպանված են:"
|
||||
},
|
||||
"legalPageKeys": ["about-us", "privacy-policy", "terms-of-service"]
|
||||
},
|
||||
"localization": {
|
||||
"defaultLocale": "en",
|
||||
"supportedLocales": ["en", "ru", "hy"],
|
||||
"currencyByLocale": {
|
||||
"en": "USD",
|
||||
"ru": "USD",
|
||||
"hy": "AMD"
|
||||
}
|
||||
},
|
||||
"apiEndpoints": {
|
||||
"bootstrap": { "path": "/bootstrap", "method": "GET", "timeoutMs": 5000 },
|
||||
"website": {
|
||||
"items": { "path": "/items", "method": "GET" },
|
||||
"categories": { "path": "/categories", "method": "GET" },
|
||||
"cart": { "path": "/cart", "method": "GET" },
|
||||
"orders": { "path": "/orders", "method": "POST" },
|
||||
"payments": { "path": "/payments", "method": "POST" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Field-by-Field Meaning
|
||||
- `schemaVersion`: bootstrap contract version used by frontend parser.
|
||||
- `generatedAt`: payload generation timestamp.
|
||||
- `tenant`: public tenant identity and locale/currency defaults.
|
||||
- `theme`: UI tokens; no business logic.
|
||||
- `layout.type`: global layout mode. Supported: `default`, `sidebar-left`, `carousel-home`, `minimal`.
|
||||
- `widgetRegistry.manifestUrl`: source for widget definitions/components mapping metadata.
|
||||
- `pages`: route-driven composition graph.
|
||||
- `footer`: footer links/icons/legal references.
|
||||
- `localization`: supported locales and currency mapping.
|
||||
- `apiEndpoints`: public endpoint mapping for frontend clients.
|
||||
|
||||
## 5. Versioning Strategy
|
||||
Use semantic versioning in `schemaVersion`:
|
||||
- Patch (`2.1.1`): non-breaking metadata additions.
|
||||
- Minor (`2.2.0`): additive fields/sections with backward compatibility.
|
||||
- Major (`3.0.0`): breaking structural changes.
|
||||
|
||||
Operational rules:
|
||||
- Keep old parser compatibility for at least one minor line.
|
||||
- Publish migration notes for any major bump.
|
||||
- Validate payload against schema before release.
|
||||
|
||||
## 6. Security Rules
|
||||
Never include in bootstrap:
|
||||
- private keys
|
||||
- internal credentials
|
||||
- admin secrets
|
||||
- payment signing material
|
||||
|
||||
Bootstrap is strictly public runtime configuration.
|
||||
118
docs/backend-platform/business-apis.md
Normal file
118
docs/backend-platform/business-apis.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Business APIs
|
||||
|
||||
## 1. Scope
|
||||
Business APIs provide transactional and catalog capabilities.
|
||||
They must remain tenant-aware and must not return UI/layout/theme/widget configuration.
|
||||
|
||||
## 2. Immutable Rule
|
||||
These APIs MUST NOT return:
|
||||
- page composition
|
||||
- layout mode
|
||||
- widget metadata
|
||||
- theme/footer/static page config
|
||||
|
||||
That data belongs to CONFIG DOMAIN (`/bootstrap`, `/pages/:slug`).
|
||||
|
||||
## 3. API Catalog
|
||||
|
||||
## /auth
|
||||
Purpose:
|
||||
- session creation/validation
|
||||
- login/logout flows
|
||||
- token refresh where applicable
|
||||
|
||||
High-level response shape:
|
||||
- session/token metadata
|
||||
- user identity claims
|
||||
- permission scopes
|
||||
|
||||
Tenant rule:
|
||||
- auth sessions are tenant-scoped by request context.
|
||||
|
||||
Must not change:
|
||||
- authentication contract and downstream payment/auth integrations.
|
||||
|
||||
## /items
|
||||
Purpose:
|
||||
- list/fetch product items
|
||||
- search/filter/sort
|
||||
- item details and availability
|
||||
|
||||
High-level response shape:
|
||||
- item arrays / item objects
|
||||
- pagination metadata
|
||||
- stock/price fields
|
||||
|
||||
Tenant rule:
|
||||
- only items visible to tenant catalog policy.
|
||||
|
||||
Must not change:
|
||||
- item identifiers/price semantics relied on frontend checkout/cart logic.
|
||||
|
||||
## /categories
|
||||
Purpose:
|
||||
- category tree retrieval
|
||||
- category filtering metadata
|
||||
|
||||
High-level response shape:
|
||||
- hierarchical or flat category collections
|
||||
- visibility and ordering metadata
|
||||
|
||||
Tenant rule:
|
||||
- category graph resolved per tenant catalog configuration.
|
||||
|
||||
Must not change:
|
||||
- category IDs and parent linkage semantics consumed by frontend domain mapping.
|
||||
|
||||
## /orders
|
||||
Purpose:
|
||||
- create and track orders
|
||||
- lifecycle state transitions
|
||||
|
||||
High-level response shape:
|
||||
- order id
|
||||
- status
|
||||
- totals and line-items
|
||||
|
||||
Tenant rule:
|
||||
- order creation/query restricted to tenant context.
|
||||
|
||||
Must not change:
|
||||
- order status lifecycle contract integrated with payment and notification flows.
|
||||
|
||||
## /cart
|
||||
Purpose:
|
||||
- cart synchronization and server-side cart state where applicable
|
||||
|
||||
High-level response shape:
|
||||
- cart items
|
||||
- totals
|
||||
- selected delivery/payment metadata
|
||||
|
||||
Tenant rule:
|
||||
- cart state must be isolated by tenant + session/user.
|
||||
|
||||
Must not change:
|
||||
- cart schema expected by checkout and payment request builders.
|
||||
|
||||
## /payments
|
||||
Purpose:
|
||||
- payment intent/QR/card flow initiation
|
||||
- payment status querying
|
||||
|
||||
High-level response shape:
|
||||
- payment id/reference
|
||||
- redirect/QR links
|
||||
- status fields
|
||||
|
||||
Tenant rule:
|
||||
- payment credentials/routes resolved per tenant context on backend.
|
||||
|
||||
Must not change:
|
||||
- existing payment provider contracts and callback/status semantics.
|
||||
|
||||
## 4. Governance for All Business APIs
|
||||
- tenant derived from request context only
|
||||
- strict repository-level tenant filtering
|
||||
- no UI config payloads
|
||||
- backward compatibility for existing frontend business flows
|
||||
75
docs/backend-platform/config-domain.md
Normal file
75
docs/backend-platform/config-domain.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Config Domain
|
||||
|
||||
## 1. Purpose
|
||||
CONFIG DOMAIN provides runtime UI configuration for a tenant.
|
||||
It enables one frontend build to serve many tenants by changing configuration, not code.
|
||||
|
||||
Primary outputs:
|
||||
- `/bootstrap`
|
||||
- `/pages/:slug` (static content domain)
|
||||
|
||||
## 2. Ownership
|
||||
Config domain owns:
|
||||
- tenant public runtime metadata
|
||||
- theme and visual tokens
|
||||
- layout mode and page structure
|
||||
- widget registry metadata pointers
|
||||
- footer metadata and legal-page mapping
|
||||
- feature flags and localization mappings
|
||||
|
||||
Business domain owns:
|
||||
- items, categories, cart, orders, payments, auth
|
||||
|
||||
## 3. Layout Engine Responsibility
|
||||
Backend returns layout intent (e.g., `layout.type`) and page graph.
|
||||
Frontend layout engine composes UI from this graph.
|
||||
|
||||
Supported modes:
|
||||
- default
|
||||
- sidebar-left
|
||||
- carousel-home
|
||||
- minimal
|
||||
|
||||
No tenant-specific UI branching in component code.
|
||||
|
||||
## 4. Widget Registry Concept
|
||||
Backend provides `widgetRegistry.manifestUrl`.
|
||||
Frontend reads manifest and resolves approved widget keys.
|
||||
|
||||
Benefits:
|
||||
- controlled extensibility
|
||||
- unknown widget safe fallback
|
||||
- decoupled rollout of widget metadata
|
||||
|
||||
## 5. Footer + Static Page System
|
||||
Footer metadata includes:
|
||||
- columns and links
|
||||
- payment icons
|
||||
- legal page references
|
||||
- localized copyright
|
||||
|
||||
Static pages provide multilingual HTML per slug.
|
||||
Frontend renders through safe sanitization path.
|
||||
|
||||
## 6. Feature Flags
|
||||
Feature flags in bootstrap:
|
||||
- enable/disable capabilities at tenant scope
|
||||
- support gradual rollout
|
||||
- avoid deployment-based behavior switches
|
||||
|
||||
## 7. Config Data vs Business Data
|
||||
Config data:
|
||||
- shapes the interface
|
||||
- relatively low-frequency changes
|
||||
- public-safe payloads
|
||||
|
||||
Business data:
|
||||
- transactional/catalog state
|
||||
- high-frequency updates
|
||||
- operational integrity requirements
|
||||
|
||||
## 8. Why Separation Matters
|
||||
- scalability: independent lifecycle for UI config and business operations
|
||||
- safety: prevents leaking operational logic into UI composition
|
||||
- maintainability: clear boundaries and lower coupling
|
||||
- multi-tenant readiness: behavior changes per tenant without code fork
|
||||
66
docs/backend-platform/deployment.md
Normal file
66
docs/backend-platform/deployment.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Backend Platform Deployment
|
||||
|
||||
## 1. Local Development Setup
|
||||
Recommended local flow:
|
||||
1. Start backend API (Node.js) with local tenant mappings.
|
||||
2. Start frontend Angular app with proxy to backend.
|
||||
3. Use local domains/hosts file entries for tenant simulation.
|
||||
|
||||
Example hosts mapping:
|
||||
- `alpha.local` -> localhost
|
||||
- `beta.local` -> localhost
|
||||
|
||||
## 2. Environment Variables (Backend)
|
||||
Infrastructure-focused variables only:
|
||||
- `PORT`
|
||||
- `NODE_ENV`
|
||||
- `DB_URL`
|
||||
- `REDIS_URL`
|
||||
- `TENANT_CACHE_TTL_SECONDS`
|
||||
- `TRUST_PROXY`
|
||||
- `ALLOWED_HOSTS`
|
||||
- `LOG_LEVEL`
|
||||
|
||||
Guideline:
|
||||
- do not use environment variables for tenant business behavior branching.
|
||||
- tenant behavior comes from tenant config data resolved by domain.
|
||||
|
||||
## 3. Frontend Proxy Setup
|
||||
Frontend proxy should route API calls to Node backend:
|
||||
- `/bootstrap`
|
||||
- `/pages/*`
|
||||
- `/items`, `/categories`, `/cart`, `/orders`, `/payments`, `/auth`
|
||||
|
||||
Proxy keeps browser-side calls same-origin in local development.
|
||||
|
||||
## 4. Production Deployment Flow
|
||||
1. Deploy stateless Node API instances.
|
||||
2. Configure ingress/load balancer to preserve host headers.
|
||||
3. Route all tenant domains to same backend/frontend runtime.
|
||||
4. Resolve tenant by domain per request.
|
||||
5. Serve tenant-specific bootstrap + tenant-scoped business responses.
|
||||
|
||||
## 5. Multi-Tenant Domain Mapping Strategy
|
||||
Maintain authoritative mapping table:
|
||||
- host -> tenantId
|
||||
- tenant status
|
||||
- locale/currency defaults
|
||||
|
||||
Operational controls:
|
||||
- admin tooling for host assignment
|
||||
- cache invalidation on mapping updates
|
||||
- audit logs for domain changes
|
||||
|
||||
## 6. Scaling Considerations (10–100+ Tenants)
|
||||
- horizontal scale backend instances
|
||||
- distributed cache for tenant + config hot paths
|
||||
- query/index optimization with tenant-partitioning strategy
|
||||
- per-tenant rate limiting and quotas
|
||||
- observability dimensions: tenantId, host, endpoint, latency, error-rate
|
||||
|
||||
## 7. Reliability Checklist
|
||||
- health/readiness probes
|
||||
- circuit breakers for downstream services
|
||||
- timeout + retry policy by endpoint class
|
||||
- graceful degradation for config fetch failures
|
||||
- rollback strategy for bad config releases
|
||||
62
docs/backend-platform/static-pages-system.md
Normal file
62
docs/backend-platform/static-pages-system.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Static Pages System
|
||||
|
||||
## 1. Endpoint
|
||||
- Method: `GET`
|
||||
- Path: `/pages/:slug`
|
||||
- Scope: tenant-aware by request domain
|
||||
|
||||
Supported slugs (example set):
|
||||
- `about-us`
|
||||
- `privacy-policy`
|
||||
- `terms-of-service`
|
||||
- `returns-policy`
|
||||
|
||||
## 2. Storage Model
|
||||
Pages are stored per tenant:
|
||||
- key: tenantId + slug
|
||||
- multilingual content map (locale -> HTML)
|
||||
- optional SEO metadata per locale
|
||||
- status (published/draft)
|
||||
|
||||
## 3. Example Response
|
||||
```json
|
||||
{
|
||||
"slug": "about-us",
|
||||
"title": {
|
||||
"en": "About Us",
|
||||
"ru": "О компании",
|
||||
"hy": "Մեր մասին"
|
||||
},
|
||||
"content": {
|
||||
"en": "<h1>About Us</h1><p>...</p>",
|
||||
"ru": "<h1>О компании</h1><p>...</p>",
|
||||
"hy": "<h1>Մեր մասին</h1><p>...</p>"
|
||||
},
|
||||
"seo": {
|
||||
"title": { "en": "About Us" },
|
||||
"description": { "en": "Company information" }
|
||||
},
|
||||
"updatedAt": "2026-07-05T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Frontend Rendering Rule
|
||||
Frontend renders static pages via safe HTML flow only:
|
||||
- resolve locale-specific HTML
|
||||
- sanitize before binding
|
||||
- bind to template as trusted/safe HTML only in static page component context
|
||||
|
||||
## 5. Security Considerations
|
||||
Backend requirements:
|
||||
- content moderation/validation pipeline
|
||||
- disallow dangerous tags/attributes at content publishing stage
|
||||
- maintain revision history and audit trail
|
||||
|
||||
Frontend requirements:
|
||||
- enforce sanitizer before rendering
|
||||
- no raw HTML injection in arbitrary components
|
||||
|
||||
Platform requirements:
|
||||
- tenant isolation on page retrieval
|
||||
- cache with tenant+slug key
|
||||
- return 404 for missing slug in tenant scope
|
||||
85
docs/backend-platform/tenant-resolution.md
Normal file
85
docs/backend-platform/tenant-resolution.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Tenant Resolution
|
||||
|
||||
## 1. Goal
|
||||
Resolve tenant from request domain and enforce strict tenant isolation for all config and business endpoints.
|
||||
|
||||
## 2. Middleware Flow
|
||||
1. Parse request host (`Host`/`X-Forwarded-Host` as trusted by ingress policy).
|
||||
2. Normalize host (lowercase, strip port, normalize punycode if needed).
|
||||
3. Resolve tenant record from host mapping store.
|
||||
4. Validate tenant status (`active`, not suspended/expired).
|
||||
5. Attach tenant context to request.
|
||||
6. Continue to route handlers with tenant-scoped services.
|
||||
|
||||
## 3. Request Context Attachment
|
||||
Attach immutable context object, e.g.:
|
||||
- `req.ctx.tenantId`
|
||||
- `req.ctx.tenantSlug`
|
||||
- `req.ctx.host`
|
||||
- `req.ctx.defaultLocale`
|
||||
- `req.ctx.allowedLocales`
|
||||
|
||||
All downstream services must read tenant from context, not from query params.
|
||||
|
||||
## 4. Pseudocode Middleware Example
|
||||
```ts
|
||||
async function tenantResolver(req, res, next) {
|
||||
const host = normalizeHost(req.headers['x-forwarded-host'] || req.headers.host);
|
||||
if (!host) return res.status(400).json({ error: 'INVALID_HOST' });
|
||||
|
||||
const cacheKey = `tenant:host:${host}`;
|
||||
let tenant = await cache.get(cacheKey);
|
||||
|
||||
if (!tenant) {
|
||||
tenant = await tenantRepository.findByHost(host);
|
||||
if (tenant) await cache.set(cacheKey, tenant, { ttlSeconds: 300 });
|
||||
}
|
||||
|
||||
if (!tenant) return res.status(404).json({ error: 'TENANT_NOT_FOUND' });
|
||||
if (!tenant.active) return res.status(403).json({ error: 'TENANT_INACTIVE' });
|
||||
|
||||
req.ctx = {
|
||||
...(req.ctx || {}),
|
||||
tenantId: tenant.id,
|
||||
tenantSlug: tenant.slug,
|
||||
host,
|
||||
defaultLocale: tenant.defaultLocale,
|
||||
allowedLocales: tenant.supportedLocales
|
||||
};
|
||||
|
||||
return next();
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Caching Strategy
|
||||
Recommended:
|
||||
- L1 in-memory cache per API instance for hot host lookups.
|
||||
- L2 distributed cache (Redis) for cross-instance consistency.
|
||||
- Cache key by host.
|
||||
- Short TTL (60-300s) + explicit invalidation on tenant changes.
|
||||
|
||||
Do not cache authorization decisions globally; only cache tenant mapping metadata.
|
||||
|
||||
## 6. Security Rules
|
||||
- Never trust tenant from client payload.
|
||||
- Always derive tenant from validated host/context.
|
||||
- Enforce tenant filter at repository layer for every query.
|
||||
- Reject cross-tenant IDs even if resource exists globally.
|
||||
- Emit audit logs for tenant mismatch attempts.
|
||||
|
||||
## 7. Edge Cases
|
||||
### Unknown Domain
|
||||
- Behavior: return `404 TENANT_NOT_FOUND`.
|
||||
- Optional: redirect only if explicit global fallback policy exists.
|
||||
|
||||
### Inactive Tenant
|
||||
- Behavior: return `403 TENANT_INACTIVE`.
|
||||
- Optional: include support contact metadata in response.
|
||||
|
||||
### Host Header Poisoning
|
||||
- Use trusted proxy chain rules.
|
||||
- Ignore untrusted forwarded host headers.
|
||||
|
||||
### Local Development Domains
|
||||
- Keep explicit local host mapping table.
|
||||
- No projectName shortcuts for tenant selection.
|
||||
Reference in New Issue
Block a user