Files
skills/plugins/document-navigator/docs/api-stack/api/api-tenant-resolver-jwt
2026-06-09 21:06:27 +01:00
..
2026-06-09 21:06:27 +01:00

@suaveplan/api-tenant-resolver-jwt — tenant-resolver adapter for the Suaveplan API plugin system. Reads the already-resolved Principal.tenantId from the per-request context (attached by an earlier api-auth-* chain) and projects it into a TenantContext. When the configured claim is present the resolver short-circuits the chain; when absent it returns null so a header/subdomain resolver can take over. Configurable claim key (default tenantId) and strict-mode (requirePrincipal: true) that throws TENANT_RESOLVER_JWT.PRINCIPAL_MISSING_TENANT_ID instead of advancing.

Stage-4 tenant resolver for the Suaveplan API plugin system. Reads the already-resolved Principal.tenantId (attached by an earlier api-auth-* chain step) and projects it into a TenantContext for the host's per-request pipeline.

import { createJwtTenantResolver } from "@suaveplan/api-tenant-resolver-jwt";

const resolver = createJwtTenantResolver();
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-jwt. Every public failure path in the resol…
    • error-codes — Defines the stable error-code registry for @suaveplan/api-tenant-resolver-jwt. Every public failure path in the resol…
  • options — Defines and validates the JwtTenantResolverOptions accepted by createJwtTenantResolver. The options shape is delibe…
    • options — Defines and validates the JwtTenantResolverOptions accepted by createJwtTenantResolver. The options shape is delibe…
  • resolver — Implements The tenant resolver factory createJwtTenantResolver. The resolver reads the tenant identifier from…
    • resolver — Implements The tenant resolver factory createJwtTenantResolver. The resolver reads the tenant identifier from…

Why a dedicated package

The tenant-resolver chain (design doc §6, step 4) runs after the session resolver. When the session is a JWT carrying tenant_id / tid, the tenant resolver's job is trivial: read principal.tenantId. Collapsing that into a dedicated adapter — rather than encoding it inside api-auth-jwt — keeps the two contracts orthogonal and lets operators chain tenant-resolver-jwt with api-tenant-resolver-header and api-tenant-resolver-subdomain (Stage 8) for the paths a JWT alone doesn't cover.

Chain interaction (forward reference)

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)

When the JWT carries no tenant claim (or the request is unauthenticated on a public route), tenant-resolver-jwt returns null and the next resolver in the chain runs. With requirePrincipal: true the chain stops here and a 401-equivalent is surfaced via TENANT_RESOLVER_JWT.PRINCIPAL_MISSING_TENANT_ID.

Options

Option Default Effect
id "jwt" Resolver id used in chain logs and metric tags.
claimKey "tenantId" Reads Principal.tenantId directly; any other key reads claims.
requirePrincipal false Throw instead of returning null when claim is absent.
getPrincipal reads req.principal Override how the principal is fetched from the request.

Error codes

  • TENANT_RESOLVER_JWT.PRINCIPAL_MISSING_TENANT_ID — strict-mode rejected.
  • TENANT_RESOLVER_JWT.INVALID_OPTIONS — construction-time Zod failure.

Exports

  • . — top-level barrel re-exporting everything below.
  • ./resolver — createJwtTenantResolver, CreateJwtTenantResolverOptions, PrincipalExtractor.
  • ./options — JwtTenantResolverOptions, parseJwtTenantResolverOptions.
  • ./error-codes — JwtTenantResolverError, TENANT_RESOLVER_JWT_CODES, JwtTenantResolverErrorCode.