Something went wrong. Try again.
your personal website on atproto - mirror blento.app
Something went wrong. Try again.
TypeScript
at create-event
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295// Authoritative DNS verification// Queries authoritative nameservers directly instead of relying on cached resolvers.// See: https://jacob.gold/posts/stop-telling-users-their-dns-is-wrong/
const TYPE_A = 1;const TYPE_NS = 2;const TYPE_CNAME = 5;
interface DnsRecord { type: number; data: string;}
// --- Wire format encoding ---
function encodeName(name: string): Uint8Array { const n = name.endsWith('.') ? name.slice(0, -1) : name; const parts = n.split('.'); const bytes: number[] = []; for (const part of parts) { bytes.push(part.length); for (let i = 0; i < part.length; i++) { bytes.push(part.charCodeAt(i)); } } bytes.push(0); return new Uint8Array(bytes);}
function buildQuery(name: string, type: number): Uint8Array { const qname = encodeName(name); const msg = new Uint8Array(12 + qname.length + 4); const id = (Math.random() * 0xffff) | 0; msg[0] = id >> 8; msg[1] = id & 0xff; msg[2] = 0x01; // RD=1 msg[5] = 0x01; // QDCOUNT=1 msg.set(qname, 12); const off = 12 + qname.length; msg[off] = type >> 8; msg[off + 1] = type & 0xff; msg[off + 3] = 0x01; // CLASS IN return msg;}
// --- Wire format decoding ---
function decodeName(data: Uint8Array, offset: number): [string, number] { const labels: string[] = []; let pos = offset; let jumped = false; let savedPos = 0; for (let safety = 0; safety < 128 && pos < data.length; safety++) { const len = data[pos]; if (len === 0) { pos++; break; } if ((len & 0xc0) === 0xc0) { if (!jumped) savedPos = pos + 2; pos = ((len & 0x3f) << 8) | data[pos + 1]; jumped = true; continue; } pos++; let label = ''; for (let i = 0; i < len && pos + i < data.length; i++) { label += String.fromCharCode(data[pos + i]); } labels.push(label); pos += len; } return [labels.join('.'), jumped ? savedPos : pos];}
function parseResponse(data: Uint8Array): DnsRecord[] { if (data.length < 12) return []; const qdcount = (data[4] << 8) | data[5]; const ancount = (data[6] << 8) | data[7]; let offset = 12;
// Skip questions for (let i = 0; i < qdcount && offset < data.length; i++) { const [, off] = decodeName(data, offset); offset = off + 4; }
const records: DnsRecord[] = []; for (let i = 0; i < ancount && offset + 10 < data.length; i++) { const [, nameEnd] = decodeName(data, offset); offset = nameEnd; const type = (data[offset] << 8) | data[offset + 1]; const rdlength = (data[offset + 8] << 8) | data[offset + 9]; offset += 10; if (offset + rdlength > data.length) break;
let value = ''; if (type === TYPE_A && rdlength === 4) { value = `${data[offset]}.${data[offset + 1]}.${data[offset + 2]}.${data[offset + 3]}`; } else if (type === TYPE_CNAME || type === TYPE_NS) { [value] = decodeName(data, offset); } if (value) records.push({ type, data: value }); offset += rdlength; } return records;}
// --- DNS-over-HTTPS (for finding NS records and as fallback) ---
interface DohAnswer { type: number; data: string;}
async function dohQuery(name: string, type: string): Promise<DohAnswer[]> { const url = `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(name)}&type=${type}`; const res = await fetch(url, { headers: { Accept: 'application/dns-json' } }); const json: { Answer?: DohAnswer[] } = await res.json(); return json.Answer ?? [];}
// --- Find authoritative nameservers (walking up the domain tree) ---
async function findNameservers(domain: string): Promise<string[]> { let current = domain; while (current.includes('.')) { const answers = await dohQuery(current, 'NS'); const ns = answers.filter((a) => a.type === TYPE_NS).map((a) => a.data.replace(/\.$/, '')); if (ns.length > 0) return ns; current = current.substring(current.indexOf('.') + 1); } return [];}
// --- Resolve hostname to IPs via DoH ---
async function resolveToIps(hostname: string): Promise<string[]> { const answers = await dohQuery(hostname, 'A'); return answers.filter((a) => a.type === TYPE_A).map((a) => a.data);}
// --- Query nameserver directly via TCP (cloudflare:sockets) ---
async function queryViaTcp(serverIp: string, name: string, type: number): Promise<DnsRecord[]> { // @ts-expect-error: cloudflare:sockets is only available in Workers runtime const { connect } = await import('cloudflare:sockets');
const query = buildQuery(name, type); const tcpMsg = new Uint8Array(query.length + 2); tcpMsg[0] = (query.length >> 8) & 0xff; tcpMsg[1] = query.length & 0xff; tcpMsg.set(query, 2);
const socket = connect({ hostname: serverIp, port: 53 }); const writer = socket.writable.getWriter(); await writer.write(tcpMsg); writer.releaseLock();
const reader = socket.readable.getReader(); let buffer = new Uint8Array(0); let responseLen = -1;
const timeout = setTimeout(() => socket.close(), 5000); try { while (true) { const { value, done } = await reader.read(); if (done) break; const chunk = new Uint8Array(value as ArrayBuffer); const newBuf = new Uint8Array(buffer.length + chunk.length); newBuf.set(buffer); newBuf.set(chunk, buffer.length); buffer = newBuf;
if (responseLen === -1 && buffer.length >= 2) { responseLen = (buffer[0] << 8) | buffer[1]; } if (responseLen >= 0 && buffer.length >= responseLen + 2) break; } } finally { clearTimeout(timeout); reader.releaseLock(); socket.close(); }
if (buffer.length < 2) return []; return parseResponse(buffer.slice(2, 2 + responseLen));}
// --- Query with TCP, fall back to DoH for local dev ---
async function queryAuthoritative( serverIp: string, name: string, type: number): Promise<DnsRecord[]> { try { return await queryViaTcp(serverIp, name, type); } catch { // cloudflare:sockets not available (local dev) — fall back to DoH const typeStr = type === TYPE_A ? 'A' : type === TYPE_CNAME ? 'CNAME' : 'NS'; return (await dohQuery(name, typeStr)).map((a) => ({ type: a.type, data: a.data })); }}
// --- Main verification ---
export interface DnsVerifyResult { ok: boolean; error?: string; hint?: string;}
function normalize(s: string): string { return s.replace(/\.$/, '').toLowerCase();}
export async function verifyDomainDns( domain: string, expectedTarget: string): Promise<DnsVerifyResult> { // Find authoritative nameservers const nameservers = await findNameservers(domain); if (nameservers.length === 0) { return { ok: false, error: `Could not find nameservers for "${domain}".` }; }
// Try each nameserver until one responds for (const ns of nameservers) { const ips = await resolveToIps(ns); if (ips.length === 0) continue;
const nsIp = ips[0];
// Check CNAME records const cnameRecords = await queryAuthoritative(nsIp, domain, TYPE_CNAME); const cnames = cnameRecords.filter((r) => r.type === TYPE_CNAME);
if (cnames.length > 0) { if (cnames.length > 1) { return { ok: false, error: `Multiple CNAME records found for "${domain}": ${cnames.map((r) => r.data).join(', ')}. Remove duplicate records so only one remains.` }; } const got = normalize(cnames[0].data); const want = normalize(expectedTarget); if (got !== want) { return { ok: false, error: `CNAME for "${domain}" points to "${got}" instead of "${want}".`, hint: `If you're using Cloudflare, make sure the proxy (orange cloud) is turned off for this record.` }; } return { ok: true }; }
// No CNAME — check A records (root/apex domains with CNAME flattening or A records) const aRecords = await queryAuthoritative(nsIp, domain, TYPE_A); const aValues = aRecords.filter((r) => r.type === TYPE_A);
if (aValues.length > 0) { // Resolve the expected target to get its IPs for comparison const expectedIps = await resolveToIps(expectedTarget); if (expectedIps.length === 0) { return { ok: false, error: `Could not resolve "${expectedTarget}" to verify A records.` }; } const expectedSet = new Set(expectedIps); const unexpected = aValues.filter((r) => !expectedSet.has(r.data)); if (unexpected.length > 0) { return { ok: false, error: `A record(s) for "${domain}" include unexpected IPs: ${unexpected.map((r) => r.data).join(', ')}. Expected only IPs matching "${expectedTarget}" (${expectedIps.join(', ')}).`, hint: `If you're using Cloudflare, make sure the proxy (orange cloud) is turned off for this record.` }; } return { ok: true }; }
// Neither CNAME nor A found return { ok: false, error: `No CNAME or A record found for "${domain}". Please add a CNAME record pointing to "${expectedTarget}".` }; }
return { ok: false, error: `Could not reach any nameserver for "${domain}".` };}