Files
2026-05-27 23:29:53 +01:00
..
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00

@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.

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

Documentation tree

  • error-codes — Defines the stable error-code registry for @suaveplan/api-tenant-resolver-header. Every public failure path raises a…
    • error-codes — Defines the stable error-code registry for @suaveplan/api-tenant-resolver-header. Every public failure path raises a…
  • instruments — Builds the OTel instrument bundle the header tenant resolver emits on every resolve() invocation. Centralising instru…
    • instruments — Builds the OTel instrument bundle the header tenant resolver emits on every resolve() invocation. Centralising instru…
  • options — Defines the Zod-validated options schema accepted by createHeaderTenantResolver. Construction-time validation guarant…
    • options — Defines the Zod-validated options schema accepted by createHeaderTenantResolver. Construction-time validation guarant…
  • resolver — Implements the Stage-8 tenant resolver factory createHeaderTenantResolver. The resolver reads the tenant identifier f…
    • resolver — Implements the Stage-8 tenant resolver factory createHeaderTenantResolver. The resolver reads the tenant identifier f…
  • trust — Encapsulates the trusted-source enforcement consulted by the header tenant resolver before it acts on a header value. T…
    • trust — Encapsulates the trusted-source enforcement consulted by the header tenant resolver before it acts on a header value. T…
  • validate — Owns the default tenant-id validator regex and the validator / registry function signatures consumed by `createHeaderTe…
    • validate — Owns the default tenant-id validator regex and the validator / registry function signatures consumed by `createHeaderTe…

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:

[ 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.