import { PlatformError, type IErrorContext } from '../errors/base.errors.js'; import type { TDomainSource } from '../../dist_ts_interfaces/data/domain.js'; /** * Domain ownership verification. * * dcrouter may only take two kinds of action on a hostname if we can prove the * zone is ours: request an ACME certificate for it, and answer DNS queries for * it authoritatively. Both used to be reachable without any ownership record at * all, which produced two production failures: * * - Routes with `tls.certificate === 'auto'` were created for zones that had no * `DomainDoc`. DNS-01 could never place the challenge TXT, so the per-domain * provisioning budget was consumed against a cause no retry can fix and the * certificates silently expired. * - `DomainDoc`s created through the ops API set `authoritative = true` * unconditionally, and older embedded SmartDNS releases treated a handler * answer as authoritative regardless of configured zones. dcrouter therefore * served apex NS records and an RFC1918 A record publicly for zones whose * real delegation belonged to third parties. * * There are exactly two proofs available in-process, neither of which an ops-API * caller can forge: * * - `provider-zone`: the zone has a `DomainDoc` with `source === 'provider'` and * a `providerId`. It only gets there through `importDomainsFromProvider()`, * which requires the zone to be listed by a credentialed provider account. * - `delegation-verified-zone`: the zone is in the DNS authority set, which a * zone only enters by having its public NS records observed naming our * nameservers. An ops-API caller cannot repoint somebody else's delegation, * so writing the record is not the same as manufacturing the proof. * * This used to read `options.dnsScopes` instead — deployment configuration, * trusted because only a redeploy could change it. That trust was real but the * cost was a second, un-reconcilable representation of DNS authority, so it is * gone: the authority set now comes from the database and carries its evidence. * * Anything else is unverified and must fail closed. */ export type TDomainOwnershipMethod = 'provider-zone' | 'delegation-verified-zone'; export type TDomainOwnershipFailure = /** No DomainDoc covers the hostname at all. */ 'no-managed-domain' /** Provider-sourced DomainDoc without a providerId — the credentialed link is gone. */ | 'provider-link-missing' /** dcrouter-hosted DomainDoc outside every verified zone: self-asserted authority. */ | 'unverified-dcrouter-zone' /** The hostname is not a usable FQDN (wildcard-only, empty, malformed labels). */ | 'invalid-hostname'; export interface IDomainOwnershipZone { name: string; source: TDomainSource; providerId?: string; } export interface IDomainOwnershipVerified { verified: true; fqdn: string; zone: string; method: TDomainOwnershipMethod; evidence: string; } export interface IDomainOwnershipUnverified { verified: false; fqdn: string; zone?: string; reason: TDomainOwnershipFailure; detail: string; } export type TDomainOwnership = IDomainOwnershipVerified | IDomainOwnershipUnverified; /** * Normalize a route/record hostname to the FQDN whose ownership must be proven. * * A wildcard is proven by the zone beneath it, so a single leading `*` is * stripped in both forms SmartProxy accepts for certificate provisioning: * `*.example.com` and the routing-glob `*example.com` (see * `normalizeDomainsForCertProvisioning` in smartproxy). A bare `*` normalizes to * nothing and is rejected — no certificate can be issued for it, so it must fail * loudly rather than reach ACME. * * Returns undefined for anything that is not a usable single hostname. */ export declare const normalizeOwnershipHostname: (hostnameArg: string) => string | undefined; /** * The authority zone covering `fqdn`, if any (the zone itself or a subzone of * one). Subzones count: being authoritative for `example.com` means * `internal.example.com` is ours too. */ export declare const findCoveringAuthorityZone: (fqdnArg: string, authorityZones?: string[]) => string | undefined; /** * Resolve whether we can prove ownership of `fqdn`. Pure: callers pass the zone * set and the authority set so this stays testable and does exactly one DB * read per audit pass rather than one per hostname. */ export declare const resolveDomainOwnership: (args: { fqdn: string; zones: IDomainOwnershipZone[]; authorityZones?: string[]; }) => TDomainOwnership; export declare const buildDomainOwnershipMessage: (ownership: IDomainOwnershipUnverified, operation: string) => string; /** * Thrown wherever an unverified domain would otherwise gain a certificate * requirement or authoritative DNS. HIGH severity so PlatformError's automatic * log lands at `error` (this must never be a debuggable-later warning), and * NON_RECOVERABLE by construction: no retry can turn an unowned domain into an * owned one, so retry layers must classify it as permanent. */ export declare class DomainOwnershipError extends PlatformError { readonly ownership: IDomainOwnershipUnverified; constructor(ownership: IDomainOwnershipUnverified, operation: string, component: string, context?: IErrorContext); protected createWithContext(context: IErrorContext): PlatformError; }