79 lines
2.7 KiB
Markdown
79 lines
2.7 KiB
Markdown
# `validate` — tenant-label validation
|
|
|
|
<!-- begin:auto-docs -->
|
|
_(no submodules)_
|
|
<!-- end:auto-docs -->
|
|
|
|
## Purpose
|
|
|
|
Provides the default RFC-1035-style hostname-label validator
|
|
(`defaultLabelValidator`) plus two pure string helpers (`normalizeHost`,
|
|
`stripBaseDomain`) that the resolver uses to take an inbound `Host`
|
|
header all the way down to a candidate tenant slug. Splitting these
|
|
helpers out of the resolver body keeps the resolver readable and the
|
|
host-parsing edge cases unit-testable in isolation.
|
|
|
|
Tenant labels are persisted as `tenantId` and compared against by
|
|
downstream RBAC / row-level filters, so the canonical form is enforced
|
|
here: lower-case ASCII, 1..63 characters, `[a-z0-9-]`, no leading or
|
|
trailing hyphen.
|
|
|
|
## Features
|
|
|
|
- `defaultLabelValidator` — RFC 1035 §2.3.1 LDH-style validator.
|
|
- `MAX_LABEL_LENGTH` constant exported for downstream schemas.
|
|
- `normalizeHost(raw)` — strip `:port` suffix, lower-case, refuse the
|
|
empty / starts-with-`:` cases.
|
|
- `stripBaseDomain(host, baseDomain)` — tri-state return:
|
|
`string` (labels), `null` (exact match — no tenant), `undefined`
|
|
(mismatch — caller decides whether to raise).
|
|
|
|
## Usage: Basic
|
|
|
|
```ts
|
|
import {
|
|
defaultLabelValidator,
|
|
normalizeHost,
|
|
stripBaseDomain,
|
|
} from "@suaveplan/api-tenant-resolver-subdomain/validate";
|
|
|
|
const host = normalizeHost("Acme.API.Example.com:443");
|
|
// → "acme.api.example.com"
|
|
const labels = stripBaseDomain(host!, "api.example.com");
|
|
// → "acme"
|
|
defaultLabelValidator(labels!); // → true
|
|
```
|
|
|
|
## Usage: Advanced
|
|
|
|
```ts
|
|
import { defaultLabelValidator } from "@suaveplan/api-tenant-resolver-subdomain/validate";
|
|
|
|
const RESERVED = new Set(["www", "admin", "api"]);
|
|
const customValidator = (label: string): boolean => {
|
|
if (!defaultLabelValidator(label)) return false;
|
|
return !RESERVED.has(label);
|
|
};
|
|
// → pass `customValidator` as `validate` in SubdomainTenantResolverOptions
|
|
```
|
|
|
|
## API Reference
|
|
|
|
- `LabelValidator` — `(label: string) => boolean`.
|
|
- `defaultLabelValidator: LabelValidator`.
|
|
- `MAX_LABEL_LENGTH: 63`.
|
|
- `normalizeHost(raw: string | null | undefined): string | null`.
|
|
- `stripBaseDomain(host, baseDomain): string | null | undefined`.
|
|
|
|
## Implementation Notes
|
|
|
|
The label validator is hand-coded against the character-code range
|
|
(`0x30..0x39`, `0x61..0x7a`, `0x2d`) rather than a regex so the
|
|
hot-path has no allocator overhead. The tri-state `stripBaseDomain`
|
|
return is the standard "missing vs absent vs malformed" pattern from
|
|
the rest of the codebase: callers MUST distinguish "host is exactly the
|
|
apex" (advance the chain — common for marketing pages) from "host has
|
|
the wrong apex" (strict-mode error). `normalizeHost` does not honour
|
|
IPv6 bracketed literals (`[::1]`) — tenant resolution by IPv6 host is
|
|
not a supported deployment shape.
|