first commit
This commit is contained in:
88
docs/api-stack/api/api-tenant-resolver-subdomain/README.md
Normal file
88
docs/api-stack/api/api-tenant-resolver-subdomain/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# @suaveplan/api-tenant-resolver-subdomain — tenant resolver for the Suaveplan API plugin system. Extracts the tenant slug from the request hostname (subdomain) — e.g. `acme.api.example.com` → tenant `acme` — strips a configured `baseDomain`, validates the remaining label, and returns a `TenantContext`. Multi-label support via `tenantLabelIndex` (default 0). `X-Forwarded-Host` is honoured only when `trustForwardedHost: true` AND the upstream IP matches `trustedProxyCidrs`; otherwise strict mode raises `TENANT_SUBDOMAIN_FORWARDED_HOST_UNTRUSTED`. Optional tenant `registry` callback for slug → id mapping. Strict-mode (`strict: true`) raises stable `TENANT_SUBDOMAIN_*` codes on every invalid path; non-strict returns `null` to advance the resolver chain.
|
||||
|
||||
Stage-8 tenant resolver for the Suaveplan API plugin system. Reads
|
||||
the inbound `Host` header (and optionally a CIDR-trusted
|
||||
`X-Forwarded-Host`), strips a configured `baseDomain`, validates the
|
||||
remaining label, and returns a `TenantContext` for the host's
|
||||
per-request pipeline.
|
||||
|
||||
```ts
|
||||
import { createSubdomainTenantResolver } from "@suaveplan/api-tenant-resolver-subdomain";
|
||||
|
||||
const resolver = createSubdomainTenantResolver({
|
||||
baseDomain: "api.example.com",
|
||||
});
|
||||
const ctx = await resolver.resolve(request);
|
||||
// → { id: "acme", slug: "acme" } | null
|
||||
```
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the namespaced `TENANT_SUBDOMAIN.*` error-code registry plus the concrete `SubdomainTenantResolverError` class…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the namespaced `TENANT_SUBDOMAIN.*` error-code registry plus the concrete `SubdomainTenantResolverError` class…
|
||||
- [forwarded](./src/forwarded/forwarded.md) — Implements CIDR-based trust enforcement for the optional `X-Forwarded-Host` header. Edge proxies (CDN, load balancer, r…
|
||||
- [forwarded](./src/forwarded/forwarded.md) — Implements CIDR-based trust enforcement for the optional `X-Forwarded-Host` header. Edge proxies (CDN, load balancer, r…
|
||||
- [options](./src/options/options.md) — Defines and validates the public option bundle accepted by `createSubdomainTenantResolver`. Every option is bounded by…
|
||||
- [options](./src/options/options.md) — Defines and validates the public option bundle accepted by `createSubdomainTenantResolver`. Every option is bounded by…
|
||||
- [otel](./src/otel/instruments.md) — Owns the single OTel counter `api.tenant.resolves_total` that the subdomain resolver increments on every invocation. Th…
|
||||
- [instruments](./src/otel/instruments.md) — Owns the single OTel counter `api.tenant.resolves_total` that the subdomain resolver increments on every invocation. Th…
|
||||
- [resolver](./src/resolver/resolver.md) — Builds the Stage-8 `TenantResolver` that derives the tenant from the inbound request's hostname (subdomain). For a host…
|
||||
- [resolver](./src/resolver/resolver.md) — Builds the Stage-8 `TenantResolver` that derives the tenant from the inbound request's hostname (subdomain). For a host…
|
||||
- [validate](./src/validate/validate.md) — Provides the default RFC-1035-style hostname-label validator (`defaultLabelValidator`) plus two pure string helpers (`n…
|
||||
- [validate](./src/validate/validate.md) — Provides the default RFC-1035-style hostname-label validator (`defaultLabelValidator`) plus two pure string helpers (`n…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Why a dedicated package
|
||||
|
||||
The tenant-resolver chain (design doc §6, step 4) runs after the
|
||||
session resolver. Public-facing deployments commonly serve every
|
||||
tenant at `<slug>.api.example.com` — the resolver job is to take the
|
||||
hostname, strip the apex, and emit a tenant slug. Collapsing that into
|
||||
a dedicated adapter — rather than baking it into `api-host` —
|
||||
keeps the contract orthogonal and lets operators chain
|
||||
`tenant-resolver-jwt`, `tenant-resolver-header`, and
|
||||
`tenant-resolver-subdomain` as separate links.
|
||||
|
||||
## Chain interaction
|
||||
|
||||
The host runs resolvers left-to-right; the first non-`null` result
|
||||
wins:
|
||||
|
||||
```
|
||||
[ tenant-resolver-jwt, tenant-resolver-header, tenant-resolver-subdomain ]
|
||||
| | |
|
||||
reads principal reads X-Tenant-Id reads sub.domain
|
||||
(typical bearer) (admin / S2S calls) (public site routes)
|
||||
```
|
||||
|
||||
When the inbound host has no leading labels (the request hit the apex
|
||||
`api.example.com`), `tenant-resolver-subdomain` returns `null` and the
|
||||
next resolver runs. With `strict: true` the chain stops here and a
|
||||
400-equivalent is surfaced via a stable `TENANT_SUBDOMAIN.*` code.
|
||||
|
||||
## Modules
|
||||
|
||||
- `error-codes/` — `TENANT_SUBDOMAIN_CODES` + `SubdomainTenantResolverError`.
|
||||
- `validate/` — RFC-1035 label validator + `normalizeHost` + `stripBaseDomain`.
|
||||
- `forwarded/` — CIDR parser + `isTrustedProxy` matcher.
|
||||
- `options/` — Zod-validated `SubdomainTenantResolverOptions`.
|
||||
- `otel/` — `api.tenant.resolves_total` counter.
|
||||
- `resolver/` — `createSubdomainTenantResolver` (the entry point).
|
||||
|
||||
## Strict mode summary
|
||||
|
||||
| Failure | non-strict | strict (raises code) |
|
||||
|----------------------------------|------------|--------------------------------------------------|
|
||||
| host missing / equals baseDomain | `null` | `null` (advance — not an error) |
|
||||
| host doesn't end with baseDomain | `null` | `TENANT_SUBDOMAIN.BASE_DOMAIN_MISMATCH` |
|
||||
| label fails validator | `null` | `TENANT_SUBDOMAIN.INVALID` |
|
||||
| registry returns null | `null` | `TENANT_SUBDOMAIN.NOT_FOUND_IN_REGISTRY` |
|
||||
| X-Forwarded-Host from untrusted | `null` | `TENANT_SUBDOMAIN.FORWARDED_HOST_UNTRUSTED` |
|
||||
| invalid options at construction | n/a | `TENANT_SUBDOMAIN.INVALID_OPTIONS` |
|
||||
|
||||
## Stability
|
||||
|
||||
`0.1.0` — additive only. `api-host` wires this when
|
||||
`host.tenant.resolvers` includes `"subdomain"`.
|
||||
|
||||
Reference in New Issue
Block a user