103 lines
7.0 KiB
Markdown
103 lines
7.0 KiB
Markdown
# @suaveplan/api-tenant-resolver-header — header-based tenant resolver for the Suaveplan API plugin system. Implements the TenantResolver contract from @suaveplan/api-types by reading the tenant identifier from a configurable HTTP header (default X-Tenant-Id), validating against a regex (default ^[a-z0-9-]{1,64}$) and an optional registry (a `lookup`-shaped TenantLookup compatible with @suaveplan/api-tenant-provider), and enforcing trusted-source metadata so end-user clients cannot spoof tenant headers. Strict mode raises namespaced TENANT_HEADER_INVALID / NOT_FOUND_IN_REGISTRY / UNTRUSTED_SOURCE errors; non-strict mode returns null so the resolver chain advances. Emits api.tenant.resolves_total{resolver="header"} counters via the OTel global meter.
|
|
|
|
Stage-8 tenant resolver. Implements the `TenantResolver` contract from
|
|
`@suaveplan/api-types/tenant-resolver` by reading the tenant identifier from
|
|
a configurable HTTP header (default `X-Tenant-Id`), validating its shape
|
|
against a regex (default `^[a-z0-9-]{1,64}$`), optionally probing a
|
|
registry, and enforcing trusted-source metadata so end-user clients cannot
|
|
spoof the tenant.
|
|
|
|
```ts
|
|
import { createHeaderTenantResolver } from "@suaveplan/api-tenant-resolver-header";
|
|
|
|
const resolver = createHeaderTenantResolver();
|
|
// service-mesh middleware stamps request.metadata.source = "service-mesh"
|
|
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 stable error-code registry for `@suaveplan/api-tenant-resolver-header`. Every public failure path raises a…
|
|
- [error-codes](./src/error-codes/error-codes.md) — Defines the stable error-code registry for `@suaveplan/api-tenant-resolver-header`. Every public failure path raises a…
|
|
- [instruments](./src/instruments/instruments.md) — Builds the OTel instrument bundle the header tenant resolver emits on every `resolve()` invocation. Centralising instru…
|
|
- [instruments](./src/instruments/instruments.md) — Builds the OTel instrument bundle the header tenant resolver emits on every `resolve()` invocation. Centralising instru…
|
|
- [options](./src/options/options.md) — Defines the Zod-validated options schema accepted by `createHeaderTenantResolver`. Construction-time validation guarant…
|
|
- [options](./src/options/options.md) — Defines the Zod-validated options schema accepted by `createHeaderTenantResolver`. Construction-time validation guarant…
|
|
- [resolver](./src/resolver/resolver.md) — Implements the Stage-8 tenant resolver factory `createHeaderTenantResolver`. The resolver reads the tenant identifier f…
|
|
- [resolver](./src/resolver/resolver.md) — Implements the Stage-8 tenant resolver factory `createHeaderTenantResolver`. The resolver reads the tenant identifier f…
|
|
- [trust](./src/trust/trust.md) — Encapsulates the trusted-source enforcement consulted by the header tenant resolver before it acts on a header value. T…
|
|
- [trust](./src/trust/trust.md) — Encapsulates the trusted-source enforcement consulted by the header tenant resolver before it acts on a header value. T…
|
|
- [validate](./src/validate/validate.md) — Owns the default tenant-id validator regex and the validator / registry function signatures consumed by `createHeaderTe…
|
|
- [validate](./src/validate/validate.md) — Owns the default tenant-id validator regex and the validator / registry function signatures consumed by `createHeaderTe…
|
|
<!-- end:auto-docs -->
|
|
|
|
## Why a dedicated package
|
|
|
|
The tenant-resolver chain (design doc §6 step 4) runs after the session
|
|
resolver. Service-to-service traffic frequently carries the tenant in an
|
|
HTTP header (`X-Tenant-Id`, `X-Acme-Tenant`, …) because no end-user session
|
|
exists to resolve it from. This adapter handles that case while preventing
|
|
header spoofing from public traffic via the trusted-source gate.
|
|
|
|
## Chain interaction
|
|
|
|
The host runs resolvers left-to-right; the first non-`null` result wins:
|
|
|
|
```text
|
|
[ api-tenant-resolver-jwt, api-tenant-resolver-header, api-tenant-resolver-subdomain ]
|
|
| | |
|
|
reads principal reads X-Tenant-Id reads sub.domain
|
|
(typical bearer) (admin / S2S calls) (public site routes)
|
|
```
|
|
|
|
`api-tenant-resolver-header` returns `null` when the header is absent, when
|
|
the request source is untrusted (non-strict), when the value fails
|
|
validation (non-strict), or when the value isn't in the registry
|
|
(non-strict). In strict mode each of those paths raises the corresponding
|
|
`TENANT_HEADER_*` code.
|
|
|
|
## Modules
|
|
|
|
- `./resolver` — `createHeaderTenantResolver`,
|
|
`CreateHeaderTenantResolverOptions`.
|
|
- `./options` — `HeaderTenantResolverOptions`,
|
|
`parseHeaderTenantResolverOptions`, `ResolvedHeaderTenantResolverOptions`.
|
|
- `./validate` — `defaultTenantIdValidator`, `DEFAULT_TENANT_ID_PATTERN`,
|
|
`TenantIdValidator`, `TenantRegistryProbe`.
|
|
- `./trust` — `defaultMetadataExtractor`, `isTrustedSource`,
|
|
`RequestMetadataExtractor`, `RequestTrustMetadata`.
|
|
- `./instruments` — `createHeaderResolverInstruments`,
|
|
`HeaderResolverInstruments`, `HeaderResolveOutcome`.
|
|
- `./error-codes` — `HeaderTenantResolverError`,
|
|
`HeaderTenantResolverErrorCode`, `TENANT_HEADER_CODES`.
|
|
|
|
## Options
|
|
|
|
| Option | Default | Effect |
|
|
| ---------------- | -------------------- | ---------------------------------------------------------------------------- |
|
|
| `id` | `"header"` | Resolver id used in chain logs and metric tags. |
|
|
| `headerName` | `"X-Tenant-Id"` | HTTP header consulted; case-insensitive. |
|
|
| `strict` | `false` | When `true`, failures raise typed errors. When `false`, failures yield `null`. |
|
|
| `trustedSources` | `["service-mesh"]` | Values for `request.metadata.source` that may set the tenant header. |
|
|
| `validate` | `^[a-z0-9-]{1,64}$` | Validator applied to the header value. |
|
|
| `registry` | `undefined` | Optional sync-or-async membership probe; runs only after validator passes. |
|
|
| `getMetadata` | reads `req.metadata` | Override for the trust-metadata extractor. |
|
|
| `instruments` | OTel global meter | Override for the instrument bundle. |
|
|
|
|
## Error codes
|
|
|
|
- `TENANT_HEADER.INVALID` — header value failed validator (strict-only).
|
|
- `TENANT_HEADER.NOT_FOUND_IN_REGISTRY` — value not in registry (strict-only).
|
|
- `TENANT_HEADER.UNTRUSTED_SOURCE` — request source not in `trustedSources`
|
|
(strict-only).
|
|
- `TENANT_HEADER.INVALID_OPTIONS` — construction-time Zod failure.
|
|
|
|
## Observability
|
|
|
|
- Counter `api.tenant.resolves_total{resolver="header", outcome}` — exactly
|
|
one increment per `resolve()` call. `outcome` is one of `resolved`,
|
|
`absent`, `invalid`, `not_found_in_registry`, `untrusted_source`.
|
|
|