{"version":3,"file":"dns.mjs","names":[],"sources":["../../../../../../src/dns/providers.ts","../../../../../../src/astro/routes/api/settings/custom-domain/dns.ts"],"sourcesContent":["/**\n * DNS providers a site owner can hand us a key for, so the custom-domain\n * records get created for them. Credentials arrive in one request, are used\n * for that request, and are never stored or logged.\n */\n\nexport interface DnsRecordSpec {\n\ttype: string;\n\tname: string;\n\tvalue: string;\n}\n\nexport interface DnsApplyResult {\n\tprovider: string;\n\tzone: string;\n\tcreated: string[];\n\tupdated: string[];\n\tunchanged: string[];\n}\n\nexport type DnsCredentials = Record<string, string>;\n\ninterface DnsProvider {\n\tid: string;\n\tlabel: string;\n\t/** Credential fields the admin form asks for, in order. */\n\tfields: Array<{ key: string; label: string; secret: boolean }>;\n\t/** Nameserver suffixes that identify a zone hosted here (lower-case). */\n\tnameservers: string[];\n\tapply(creds: DnsCredentials, records: DnsRecordSpec[]): Promise<DnsApplyResult>;\n}\n\nclass DnsProviderError extends Error {}\n\nconst UA = \"premium-cms/1.0 (custom-domain dns setup)\";\nconst TRAILING_DOT = /\\.$/;\nconst TXT_QUOTES = /^\"|\"$/g;\n\nfunction normalizeName(name: string): string {\n\treturn name.trim().replace(TRAILING_DOT, \"\").toLowerCase();\n}\n\n/** Candidate zones for a hostname, longest first: a.b.example.com → [a.b.example.com, b.example.com, example.com]. */\nfunction zoneCandidates(hostname: string): string[] {\n\tconst labels = normalizeName(hostname).split(\".\");\n\tconst out: string[] = [];\n\tfor (let i = 0; i < labels.length - 1; i++) out.push(labels.slice(i).join(\".\"));\n\treturn out;\n}\n\nfunction sameValue(type: string, a: string, b: string): boolean {\n\tconst na = a.trim().replace(TRAILING_DOT, \"\");\n\tconst nb = b.trim().replace(TRAILING_DOT, \"\");\n\treturn type === \"TXT\"\n\t\t? na.replace(TXT_QUOTES, \"\") === nb.replace(TXT_QUOTES, \"\")\n\t\t: na.toLowerCase() === nb.toLowerCase();\n}\n\n/* ── Cloudflare ────────────────────────────────────────────────────── */\n\nasync function cf<T>(token: string, method: string, path: string, body?: unknown): Promise<T> {\n\tconst res = await fetch(`https://api.cloudflare.com/client/v4${path}`, {\n\t\tmethod,\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"User-Agent\": UA,\n\t\t},\n\t\tbody: body === undefined ? undefined : JSON.stringify(body),\n\t});\n\tconst data = (await res.json().catch(() => null)) as {\n\t\tsuccess?: boolean;\n\t\tresult?: T;\n\t\terrors?: Array<{ code: number; message: string }>;\n\t} | null;\n\tif (!res.ok || !data?.success) {\n\t\tconst msg = data?.errors?.map((e) => e.message).join(\"; \") || `HTTP ${res.status}`;\n\t\tif (data?.errors?.some((e) => e.code === 6003 || e.code === 6111)) {\n\t\t\tthrow new DnsProviderError(\n\t\t\t\t\"Cloudflare didn't accept that as an API token. Use an API Token (My Profile → API Tokens → Create, with Zone → DNS → Edit), not the Global API Key.\",\n\t\t\t);\n\t\t}\n\t\tif (res.status === 401 || res.status === 403)\n\t\t\tthrow new DnsProviderError(`Cloudflare rejected the token: ${msg}`);\n\t\tthrow new DnsProviderError(`Cloudflare: ${msg}`);\n\t}\n\treturn data.result as T;\n}\n\nconst cloudflare: DnsProvider = {\n\tid: \"cloudflare\",\n\tlabel: \"Cloudflare\",\n\tfields: [{ key: \"token\", label: \"API token (Zone → DNS → Edit)\", secret: true }],\n\tnameservers: [\".ns.cloudflare.com\"],\n\tasync apply(creds, records) {\n\t\tconst token = (creds.token ?? \"\").trim();\n\t\tif (!token) throw new DnsProviderError(\"An API token is required.\");\n\t\tconst host = records[0]?.name ?? \"\";\n\t\tlet zone: { id: string; name: string } | undefined;\n\t\tfor (const cand of zoneCandidates(host)) {\n\t\t\tconst zones = await cf<Array<{ id: string; name: string }>>(\n\t\t\t\ttoken,\n\t\t\t\t\"GET\",\n\t\t\t\t`/zones?name=${encodeURIComponent(cand)}`,\n\t\t\t);\n\t\t\tif (zones[0]) {\n\t\t\t\tzone = zones[0];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!zone)\n\t\t\tthrow new DnsProviderError(`No Cloudflare zone for ${host} is reachable with this token.`);\n\t\tconst out: DnsApplyResult = {\n\t\t\tprovider: \"cloudflare\",\n\t\t\tzone: zone.name,\n\t\t\tcreated: [],\n\t\t\tupdated: [],\n\t\t\tunchanged: [],\n\t\t};\n\t\tfor (const r of records) {\n\t\t\tconst name = normalizeName(r.name);\n\t\t\tconst existing = await cf<Array<{ id: string; content: string; proxied?: boolean }>>(\n\t\t\t\ttoken,\n\t\t\t\t\"GET\",\n\t\t\t\t`/zones/${zone.id}/dns_records?type=${r.type}&name=${encodeURIComponent(name)}`,\n\t\t\t);\n\t\t\tconst label = `${r.type} ${name}`;\n\t\t\t// Custom hostnames must stay DNS-only at the customer's zone: a\n\t\t\t// proxied CNAME would terminate at their Cloudflare, not ours.\n\t\t\tconst body = { type: r.type, name, content: r.value, ttl: 300, proxied: false };\n\t\t\tconst match = existing.find((e) => sameValue(r.type, e.content, r.value));\n\t\t\tif (existing.length === 0) {\n\t\t\t\tawait cf(token, \"POST\", `/zones/${zone.id}/dns_records`, body);\n\t\t\t\tout.created.push(label);\n\t\t\t} else if (match && !match.proxied) {\n\t\t\t\tout.unchanged.push(label);\n\t\t\t} else if (match) {\n\t\t\t\t// Right target, but proxied: the customer's Cloudflare would terminate\n\t\t\t\t// the request itself and never reach our fallback origin (a 522).\n\t\t\t\tawait cf(token, \"PATCH\", `/zones/${zone.id}/dns_records/${match.id}`, { proxied: false });\n\t\t\t\tout.updated.push(`${label} (proxy turned off)`);\n\t\t\t} else {\n\t\t\t\tawait cf(token, \"PUT\", `/zones/${zone.id}/dns_records/${existing[0]!.id}`, body);\n\t\t\t\tout.updated.push(label);\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t},\n};\n\n/* ── Simply.com ────────────────────────────────────────────────────── */\n\nasync function simply<T>(auth: string, method: string, path: string, body?: unknown): Promise<T> {\n\tconst res = await fetch(`https://api.simply.com/2${path}`, {\n\t\tmethod,\n\t\theaders: { Authorization: auth, \"Content-Type\": \"application/json\", \"User-Agent\": UA },\n\t\tbody: body === undefined ? undefined : JSON.stringify(body),\n\t});\n\tconst data = (await res.json().catch(() => null)) as (T & { message?: string }) | null;\n\tif (!res.ok) {\n\t\tif (res.status === 401 || res.status === 403)\n\t\t\tthrow new DnsProviderError(\"Simply.com rejected the account/API key.\");\n\t\tif (res.status === 404)\n\t\t\tthrow new DnsProviderError(\"Simply.com: domain not found on this account.\");\n\t\tthrow new DnsProviderError(`Simply.com: ${data?.message || `HTTP ${res.status}`}`);\n\t}\n\treturn data as T;\n}\n\nconst simplyCom: DnsProvider = {\n\tid: \"simply\",\n\tlabel: \"Simply.com\",\n\tfields: [\n\t\t{ key: \"account\", label: \"Account name (S123456)\", secret: false },\n\t\t{ key: \"apiKey\", label: \"API key\", secret: true },\n\t],\n\tnameservers: [\".simply.com\", \".unoeuro.com\"],\n\tasync apply(creds, records) {\n\t\tconst account = (creds.account ?? \"\").trim();\n\t\tconst apiKey = (creds.apiKey ?? \"\").trim();\n\t\tif (!account || !apiKey) throw new DnsProviderError(\"Account name and API key are required.\");\n\t\tconst auth = `Basic ${btoa(`${account}:${apiKey}`)}`;\n\t\tconst host = records[0]?.name ?? \"\";\n\t\tlet zone: string | undefined;\n\t\tlet existing: Array<{ record_id: number; name: string; type: string; data: string }> = [];\n\t\tfor (const cand of zoneCandidates(host)) {\n\t\t\ttry {\n\t\t\t\tconst r = await simply<{ records?: typeof existing }>(\n\t\t\t\t\tauth,\n\t\t\t\t\t\"GET\",\n\t\t\t\t\t`/my/products/${encodeURIComponent(cand)}/dns/records/`,\n\t\t\t\t);\n\t\t\t\tzone = cand;\n\t\t\t\texisting = r.records ?? [];\n\t\t\t\tbreak;\n\t\t\t} catch (e) {\n\t\t\t\tif (!(e instanceof DnsProviderError) || !e.message.includes(\"not found\")) throw e;\n\t\t\t}\n\t\t}\n\t\tif (!zone) throw new DnsProviderError(`No Simply.com DNS zone for ${host} on this account.`);\n\t\tconst out: DnsApplyResult = {\n\t\t\tprovider: \"simply\",\n\t\t\tzone,\n\t\t\tcreated: [],\n\t\t\tupdated: [],\n\t\t\tunchanged: [],\n\t\t};\n\t\tfor (const r of records) {\n\t\t\tconst name = normalizeName(r.name);\n\t\t\tconst label = `${r.type} ${name}`;\n\t\t\tconst match = existing.filter((e) => e.type === r.type && normalizeName(e.name) === name);\n\t\t\tconst body = { type: r.type, name, data: r.value, ttl: 300 };\n\t\t\tif (match.length === 0) {\n\t\t\t\tawait simply(auth, \"POST\", `/my/products/${encodeURIComponent(zone)}/dns/records/`, body);\n\t\t\t\tout.created.push(label);\n\t\t\t} else if (match.some((e) => sameValue(r.type, e.data, r.value))) {\n\t\t\t\tout.unchanged.push(label);\n\t\t\t} else {\n\t\t\t\tawait simply(\n\t\t\t\t\tauth,\n\t\t\t\t\t\"PUT\",\n\t\t\t\t\t`/my/products/${encodeURIComponent(zone)}/dns/records/${match[0]!.record_id}/`,\n\t\t\t\t\tbody,\n\t\t\t\t);\n\t\t\t\tout.updated.push(label);\n\t\t\t}\n\t\t}\n\t\treturn out;\n\t},\n};\n\nexport const DNS_PROVIDERS: Record<string, DnsProvider> = {\n\t[cloudflare.id]: cloudflare,\n\t[simplyCom.id]: simplyCom,\n};\n\n/** Provider catalogue for the admin form (no secrets). */\nexport function listDnsProviders(): Array<{\n\tid: string;\n\tlabel: string;\n\tfields: DnsProvider[\"fields\"];\n}> {\n\treturn Object.values(DNS_PROVIDERS).map(({ id, label, fields }) => ({ id, label, fields }));\n}\n\n/**\n * Guess the DNS provider of a hostname from the nameservers of the closest\n * zone that has any (DNS-over-HTTPS, so it works from a Worker). Returns the\n * provider id, or null when the nameservers belong to none we support.\n */\nexport async function detectDnsProvider(\n\thostname: string,\n): Promise<{ provider: string | null; nameservers: string[] }> {\n\tfor (const zone of zoneCandidates(hostname)) {\n\t\tlet answers: Array<{ type: number; data: string }> = [];\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(zone)}&type=NS`,\n\t\t\t\t{ headers: { accept: \"application/dns-json\", \"User-Agent\": UA } },\n\t\t\t);\n\t\t\tanswers = ((await res.json()) as { Answer?: typeof answers }).Answer ?? [];\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tconst ns = answers\n\t\t\t.filter((a) => a.type === 2)\n\t\t\t.map((a) => a.data.toLowerCase().replace(TRAILING_DOT, \"\"));\n\t\tif (ns.length === 0) continue;\n\t\tconst hit = Object.values(DNS_PROVIDERS).find((p) =>\n\t\t\tns.some((n) => p.nameservers.some((suffix) => n.endsWith(suffix))),\n\t\t);\n\t\treturn { provider: hit?.id ?? null, nameservers: ns };\n\t}\n\treturn { provider: null, nameservers: [] };\n}\n\nexport async function applyDnsRecords(\n\tproviderId: string,\n\tcreds: DnsCredentials,\n\trecords: DnsRecordSpec[],\n): Promise<DnsApplyResult> {\n\tconst provider = DNS_PROVIDERS[providerId];\n\tif (!provider) throw new DnsProviderError(`Unknown DNS provider \"${providerId}\".`);\n\tif (records.length === 0) throw new DnsProviderError(\"There are no records to add.\");\n\treturn provider.apply(creds, records);\n}\n\nexport { DnsProviderError };\n","/**\n * Create the custom-domain DNS records at the owner's DNS provider.\n *\n * POST /_emdash/api/settings/custom-domain/dns\n *   { domain, provider: \"cloudflare\" | \"simply\", credentials: { ... } }\n * GET  /_emdash/api/settings/custom-domain/dns?domain=…\n *   → the provider catalogue, plus which one the domain's nameservers point at\n *\n * The credentials live in this one request: they are used to write the\n * records the hosting platform asks for (fetched fresh from it) and are\n * neither stored nor logged. The caller re-checks the domain afterwards.\n */\n\nimport type { APIRoute } from \"astro\";\nimport { z } from \"zod\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { NotManagedError, platformCustomDomain } from \"../../../../../dns/platform.js\";\nimport {\n\tapplyDnsRecords,\n\tdetectDnsProvider,\n\tDnsProviderError,\n\tlistDnsProviders,\n} from \"../../../../../dns/providers.js\";\n\nexport const prerender = false;\n\nconst BodySchema = z.object({\n\tdomain: z.string().trim().min(1).max(253),\n\tprovider: z.string().trim().min(1).max(40),\n\tcredentials: z.record(z.string(), z.string().max(4096)),\n});\n\nexport const GET: APIRoute = async ({ locals, url }) => {\n\tconst denied = requirePerm(locals.user, \"settings:manage\");\n\tif (denied) return denied;\n\tconst domain = (url.searchParams.get(\"domain\") ?? \"\").trim().slice(0, 253);\n\tconst detected = domain ? await detectDnsProvider(domain) : { provider: null, nameservers: [] };\n\treturn apiSuccess({ providers: listDnsProviders(), detected: detected.provider, nameservers: detected.nameservers });\n};\n\nexport const POST: APIRoute = async ({ locals, request }) => {\n\tconst { emdash, user } = locals;\n\tif (!emdash?.db) return apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\n\tconst denied = requirePerm(user, \"settings:manage\");\n\tif (denied) return denied;\n\n\tconst body = await parseBody(request, BodySchema);\n\tif (isParseError(body)) return body;\n\n\ttry {\n\t\tconst check = await platformCustomDomain(emdash.db, { domain: body.domain, action: \"check\" });\n\t\tif (!check.success) return apiError(\"CUSTOM_DOMAIN_ERROR\", check.error ?? \"Check failed\", 502);\n\t\tif (check.active) return apiSuccess({ alreadyActive: true });\n\t\tconst records = (check.records ?? []).map((r) => ({ type: r.type, name: r.name, value: r.value }));\n\t\tconst applied = await applyDnsRecords(body.provider, body.credentials, records);\n\t\treturn apiSuccess(applied);\n\t} catch (error) {\n\t\tif (error instanceof NotManagedError) return apiError(\"NOT_MANAGED\", error.message, 400);\n\t\tif (error instanceof DnsProviderError) return apiError(\"DNS_PROVIDER_ERROR\", error.message, 400);\n\t\treturn handleError(error, \"Could not add the DNS records\", \"DNS_SETUP_ERROR\");\n\t}\n};\n"],"mappings":"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"}