2.3 KiB
2.3 KiB
@suaveplan/api-tenant-resolver-subdomain — subdomain tenant resolver
Package: @suaveplan/api-tenant-resolver-subdomain
Status: PROPOSED
Type: new-package
Stage: 8 — see umbrella openspec/changes/api-plugin-foundation/
Why
Per design doc §6 step 4, multi-tenant deployments commonly serve each tenant at <tenant>.api.example.com. This adapter resolves the tenant from the request's host header, completing the v1 tenant-resolver chain alongside the header adapter.
What changes
- ADDED:
createSubdomainTenantResolver({ baseDomain, validate, registry })— readsrequest.host(orX-Forwarded-Hostwhen configured), stripsbaseDomain, validates the remaining label, and returns{ tenantId, source: "subdomain" }ornull. - ADDED: Multi-label support —
tenantLabelIndexconfig selects which label is the tenant (default 0 — leftmost). Supports nested deployments (<tenant>.<region>.api.example.com). - ADDED: Forwarded-host trust —
trustForwardedHostconfig defaults tofalse(don't trust client-supplied headers); whentrue, consultstrustedProxyCidrsto validate the upstream IP. - ADDED: Strict mode parity with
api-tenant-resolver-header. - ADDED: Conformance — passes
runTenantResolverConformance. - ADDED: Error codes (
TENANT_SUBDOMAIN_*):INVALID,NOT_FOUND_IN_REGISTRY,BASE_DOMAIN_MISMATCH,FORWARDED_HOST_UNTRUSTED.
Out of scope
- TLS / cert provisioning per tenant — operator concern.
- Reverse-DNS resolution — out of scope; we only look at the request host.
Source mapping
Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.
| Requirement | Design doc section |
|---|---|
| Tenant resolver chain | §6 step 4 |
Verification approach
src/resolver/resolver.test.ts—acme.api.example.com→acme; mismatched base domain → strict raisesTENANT_SUBDOMAIN_BASE_DOMAIN_MISMATCH; multi-label index.src/forwarded/forwarded.test.ts— trusted-proxy CIDR check; untrusted proxy ignored.src/resolver/resolver.spec-conformance.test.ts— runsrunTenantResolverConformance.tests/dep-graph.test.ts.
Migration impact
None — additive only. api-host wires this when host.tenant.resolvers includes "subdomain".