This repository has no description
flarebot docs custom-domains.md
7.6 kB

Custom domains #

An installed Flarebot can optionally use one custom hostname in an active zone in the installation's Cloudflare account. Wildcards, URLs with paths or ports, IP addresses, and workers.dev aliases are rejected. Domains not yet on Cloudflare need separate zone/registrar onboarding. Flarebot does not register domains or change nameservers. Existing DNS records and other Workers are never replaced.

Setup and publisher prerequisites #

Choose Settings → Domain → Configure domain, or Choose your address on a ready installation. The Flarebot publisher screen lists eligible zones, accepts a hostname, and shows progress while Cloudflare prepares HTTPS. The page can be closed without stopping the operation. Reconnecting Cloudflare may require an OAuth consent redirect; only a fixed installation ID is retained through that flow. An active hostname is offered as the preferred application address. Its host-only session is independent of the original workers.dev session.

The customer must first have a release containing domain configuration support. Keep the publisher signing key stable and the customer's pinned key and session secret unchanged. An older customer must be upgraded; adding routing alone does not make that customer support a second authentication origin.

Domain management is optional and fails closed with setup_required until the publisher reviews and registers the actual OAuth catalog scopes covering:

Capability Required operations
domain-zones List/read active zones in the installation account; read DNS records to detect conflicts.
domain-routing List/read Worker Custom Domains; script-scoped domain records attachment with conflict override disabled; detach a saved Custom Domain.

Add these capability mappings to the existing oauthCapabilities.scopes manifest, keeping oauthScopes and the registered client's scope IDs consistent. These are Flarebot capability names, not OAuth scope IDs. No guessed scope mapping or additional default grant is installed by this feature. Existing sign-in and deployment remain valid with the original capability set. Test an authorized live grant against the exact endpoints before enabling the feature for customers.

Cloudflare documents Custom Domains as automatically managing DNS and certificates. Attachment uses the same native script domain records interface as Wrangler: PUT /accounts/:account/workers/scripts/:script/domains/records with override_scope: false, override_existing_origin: false, and override_existing_dns_record: false. Unlike Wrangler's noninteractive defaults, Flarebot never turns on takeover flags. The scope flag preserves unrelated domains. DNS preflight is advisory; the write-time flags also protect against conflicts appearing after the check. This interface and its scope mapping require live certification; local fixtures do not prove Cloudflare entitlement or TLS.

Ownership and recovery #

InstallationRegistry owns a separate domain:<installationId> record and per-installation request replay records. The immutable resources.runtimeOrigin remains the workers.dev management/recovery origin. A domain record contains only hostname, zone/domain IDs, operation identity, revision, deadline, intent, status, and a declared error category. No credential or grant reference is stored there.

The existing native InstallationWorkflow runs domain operations with kind: "domain". It commits write intent before attachment, reconciles uncertain provider replies, delivers a signed revisioned configuration to the runtime, checks the account's domain mapping and signed-challenge HTTPS endpoint, then marks the domain active. Browser polling observes metadata; it does not drive execution. Tokens remain in the expiring encrypted AuthVault and never enter Workflow inputs, results, or logs. Upgrade and domain mutations cannot start over each other's active installation operation.

The customer persists the allowed origin in a dedicated PersonalAgent SQLite table. The management-only receipt uses a separate assertion purpose and pinned issuer, owner, installation and audience. Lower revisions and conflicting same-revision receipts are rejected; exact retries are idempotent. This state survives code upgrades without changing deployment configuration or user secrets. The login bridge validates an explicitly requested origin both when issuing and exchanging a one-time code. Login challenges, session audiences, mutation Origin headers and WebSockets remain bound to the exact origin being used.

Removing a domain first removes it from login eligibility, then revokes the runtime origin, then detaches the saved mapping. The workers.dev address remains available throughout. Failed preflight cancellation never deletes a pre-existing domain. Cancellation is rejected while attachment is connecting or its outcome is unresolved. An explicit restart terminates the old Workflow and preserves write intent. Only one PUT can follow an intent: after a lost reply, retries observe the same hostname and never issue a second PUT. A definitive rejected write can be retried normally. An indeterminate write with no observed mapping stays attachment_outcome_unknown; it cannot be called removed or release its hostname slot until reconciled. This includes the rare crash between committing intent and sending the request: absence cannot distinguish that crash from a delayed accepted write. workers.dev remains usable; publisher/Cloudflare investigation may be needed if the outcome never becomes observable.

Browser interface and checks #

  • GET /api/installations/:id/domain: owned metadata; identity session only.
  • GET /api/installations/:id/domain/zones: eligible zones; current grant required.
  • POST /api/installations/:id/domain: exact form requestId, zoneId, hostname.
  • POST /api/installations/:id/domain/remove or /retry: exact form requestId.
  • GET /api/domain on the customer: authenticated setup link and configured origin.

All publisher mutations retain exact-Origin protection and revalidate the grant against the installation's stored account, not the account picker selection. HTTP/status errors expose only declared categories. Reuse the saved request ID after an uncertain response; retries after an observed failure start a new operation for the same hostname. A DNS conflict must be resolved explicitly or the setup cancelled before choosing another hostname.

Build sequentially: pnpm build:release, then pnpm build:control-plane:fixture for a dirty local checkout (never publish this fixture catalog). Run pnpm test:domains, pnpm test:bridge, and pnpm typecheck. Domain tests use native SQLite DOs and Workflows with an explicit Cloudflare network fixture; the bridge suite exercises real two-site sessions, custom origin isolation, signed configuration replay/removal and persistence. Playwright renders setup, pending HTTPS, active, conflict, reconnect and removal states. These tests do not certify a live OAuth grant, DNS propagation or edge TLS.