3.2 KiB
Capability Spec Delta: api-tenant-resolver-subdomain (api-tenant-resolver-subdomain)
Package: @suaveplan/api-tenant-resolver-subdomain
Status: PROPOSED
Every requirement below carries a
**Verified by:**line at authoring time.
ADDED Requirements
Requirement: Resolves tenant from request host subdomain
The package SHALL expose createSubdomainTenantResolver({ baseDomain, validate, registry }) that reads request.host, strips baseDomain, validates the remaining label, and returns { tenantId, source: "subdomain" } when valid.
Scenario: leftmost label resolves as tenant
- WHEN
request.host = "acme.api.example.com"ANDbaseDomain = "api.example.com" - THEN the resolver returns
{ tenantId: "acme", source: "subdomain" }
Verified by: src/resolver/resolver.test.ts::"leftmost label resolves as tenant"
Requirement: Base-domain mismatch handled per strict mode
The package SHALL raise TENANT_SUBDOMAIN_BASE_DOMAIN_MISMATCH in strict mode when the host does not end with baseDomain. In non-strict mode it returns null.
Scenario: mismatched base domain raises in strict
- WHEN
request.host = "acme.evil.example.com"ANDbaseDomain = "api.example.com"ANDstrict: true - THEN the resolver raises
TENANT_SUBDOMAIN_BASE_DOMAIN_MISMATCH
Verified by: src/resolver/resolver.test.ts::"mismatched base domain raises in strict"
Requirement: Multi-label deployments via tenantLabelIndex
The package SHALL accept tenantLabelIndex (default 0) to support hosts like <tenant>.<region>.api.example.com.
Scenario: middle-label deployment
- WHEN
request.host = "acme.eu.api.example.com"ANDtenantLabelIndex = 0ANDbaseDomain = "api.example.com" - THEN tenant is
acme(label 0 ofacme.eu)
Verified by: src/resolver/resolver.test.ts::"multi-label deployment resolves by index"
Requirement: X-Forwarded-Host trust requires CIDR allowlist
The package SHALL only consult X-Forwarded-Host when trustForwardedHost: true AND the upstream IP matches trustedProxyCidrs. Untrusted upstream raises TENANT_SUBDOMAIN_FORWARDED_HOST_UNTRUSTED in strict mode.
Scenario: untrusted proxy ignored
- WHEN
X-Forwarded-Hostis set AND the upstream IP is not intrustedProxyCidrsANDstrict: true - THEN the resolver raises
TENANT_SUBDOMAIN_FORWARDED_HOST_UNTRUSTED
Scenario: trusted proxy honoured
- WHEN the upstream IP matches
trustedProxyCidrsANDX-Forwarded-Hostis well-formed - THEN the forwarded host is used to resolve the tenant
Verified by: src/forwarded/forwarded.test.ts::"untrusted proxy raises FORWARDED_HOST_UNTRUSTED" AND src/forwarded/forwarded.test.ts::"trusted proxy honours forwarded host"
Requirement: Passes runTenantResolverConformance
The package SHALL pass runTenantResolverConformance from @suaveplan/api-tenant-provider/testing.
Scenario: conformance passes
- WHEN
runTenantResolverConformance("subdomain", makeSubdomainResolver)runs - THEN every harness
it(...)passes
Verified by: src/resolver/resolver.spec-conformance.test.ts::"subdomain resolver passes runTenantResolverConformance"