/** * Stable ID for the built-in DcRouter pseudo-provider. The Providers list * surfaces this as the first, non-deletable row so operators see a uniform * "who serves this?" answer for every domain. The ID is magic — it never * exists in the DnsProviderDoc collection; handlers inject it at read time * and reject any mutation that targets it. */ export const DCROUTER_BUILTIN_PROVIDER_ID = '__dcrouter__'; /** * Supported DNS provider types. * * - 'cloudflare' → Cloudflare account (API token-based). Provider stays * authoritative; dcrouter pushes record changes via API. * - 'dcrouter' → Built-in pseudo-provider for dcrouter-hosted zones. * dcrouter itself is the authoritative DNS server. No * credentials, cannot be created/edited/deleted through * the provider CRUD — the Providers view renders it from * a handler-level synthetic row. * * The abstraction is designed so additional providers (Route53, Gandi, * DigitalOcean, foreign dcrouters…) can be added by implementing the * IDnsProvider class interface in ts/dns/providers/. */ export type TDnsProviderType = 'cloudflare' | 'dcrouter'; /** * Status of the last connection test against a provider. */ export type TDnsProviderStatus = 'untested' | 'ok' | 'error'; /** * Cloudflare-specific credential shape. */ export interface ICloudflareCredentials { apiToken: string; } /** * Discriminated union of all supported provider credential shapes. * Persisted opaquely on `IDnsProvider.credentials`. */ export type TDnsProviderCredentials = | ({ type: 'cloudflare' } & ICloudflareCredentials); /** * A registered DNS provider account. Holds the credentials needed to * call the provider's API and a snapshot of its last health check. */ export interface IDnsProvider { id: string; name: string; type: TDnsProviderType; /** Opaque credentials object — shape depends on `type`. */ credentials: TDnsProviderCredentials; status: TDnsProviderStatus; lastTestedAt?: number; lastError?: string; createdAt: number; updatedAt: number; createdBy: string; } /** * A redacted view of IDnsProvider safe to send to the UI / over the wire. * Strips secret fields from `credentials` while preserving the rest. */ export interface IDnsProviderPublic { id: string; name: string; type: TDnsProviderType; status: TDnsProviderStatus; lastTestedAt?: number; lastError?: string; createdAt: number; updatedAt: number; createdBy: string; /** Whether credentials are configured (true after creation). Never the secret itself. */ hasCredentials: boolean; /** * True for the built-in DcRouter pseudo-provider — read-only, cannot be * created / edited / deleted. Injected by the handler layer, never * persisted in the DnsProviderDoc collection. */ builtIn?: boolean; } /** * A domain reported by a provider's API (not yet imported into dcrouter). */ export interface IProviderDomainListing { /** FQDN of the zone (e.g. 'example.com'). */ name: string; /** Provider's internal zone identifier (zone_id for Cloudflare). */ externalId: string; /** Authoritative nameservers reported by the provider. */ nameservers: string[]; } /** * Schema entry for a single credential field, used by the OpsServer UI to * render a provider's credential form dynamically. */ export interface IDnsProviderCredentialField { /** Key under which the value is stored in the credentials object. */ key: string; /** Label shown to the user. */ label: string; /** Optional inline help text. */ helpText?: string; /** Whether the field must be filled. */ required: boolean; /** True for secret fields (rendered as password input, never echoed back). */ secret: boolean; } /** * Metadata describing a DNS provider type. Drives: * - the OpsServer UI's provider type picker + credential form, * - documentation of which credentials each provider needs, * - end-to-end consistency between the type union, the discriminated * credentials union, the runtime factory, and the form rendering. * * To add a new provider, append a new entry to `dnsProviderTypeDescriptors` * below — and follow the checklist in `ts/dns/providers/factory.ts`. */ export interface IDnsProviderTypeDescriptor { type: TDnsProviderType; /** Human-readable name for the UI. */ displayName: string; /** One-line description shown next to the type picker. */ description: string; /** Schema for the credentials form. */ credentialFields: IDnsProviderCredentialField[]; } /** * Single source of truth for which DNS provider types exist and what * credentials each one needs. Used by both backend and frontend. */ export const dnsProviderTypeDescriptors: ReadonlyArray = [ { type: 'dcrouter', displayName: 'DcRouter (built-in)', description: 'Built-in authoritative DNS. Records are served directly by dcrouter — delegate the domain\'s NS records to make this effective.', credentialFields: [], }, { type: 'cloudflare', displayName: 'Cloudflare', description: 'Manages records via the Cloudflare API. Provider stays authoritative; dcrouter pushes record changes.', credentialFields: [ { key: 'apiToken', label: 'API Token', helpText: 'A Cloudflare API token with Zone:Read and DNS:Edit permissions for the target zones.', required: true, secret: true, }, ], }, ]; /** * Look up the descriptor for a given provider type. */ export function getDnsProviderTypeDescriptor( type: TDnsProviderType, ): IDnsProviderTypeDescriptor | undefined { return dnsProviderTypeDescriptors.find((d) => d.type === type); }