From ea9934c42427a21e1d52ae07bcdd5eccd41904a6 Mon Sep 17 00:00:00 2001 From: Rahul Mishra Date: Wed, 28 Aug 2024 23:15:28 +0530 Subject: [PATCH] chore: instrument dns check functions (#753) --- apps/mail-bridge/postal-db/functions.ts | 419 ++++++++++++----------- apps/platform/utils/updateDnsRecords.ts | 435 +++++++++++++----------- apps/worker/functions/check-dns.ts | 134 ++++---- apps/worker/services/dns-check-queue.ts | 12 +- 4 files changed, 528 insertions(+), 472 deletions(-) diff --git a/apps/mail-bridge/postal-db/functions.ts b/apps/mail-bridge/postal-db/functions.ts index 9e66c203..5d3704af 100644 --- a/apps/mail-bridge/postal-db/functions.ts +++ b/apps/mail-bridge/postal-db/functions.ts @@ -27,7 +27,9 @@ import { } from './generators'; import { connection as rawMySqlConnection, postalDB } from '.'; import type { TypeId } from '@u22n/utils/typeid'; +import { getTracer } from '@u22n/otel/helpers'; import { discord } from '@u22n/utils/discord'; +import { flatten } from '@u22n/otel/exports'; import { and, eq, sql } from 'drizzle-orm'; import { randomUUID } from 'node:crypto'; import { env } from '../env'; @@ -169,242 +171,255 @@ export type GetDomainDNSRecordsOutput = } | { error: string; errorCode: number }; +const tracer = getTracer('mail-bridge/dns-fetcher'); + export async function getDomainDNSRecords( domainId: string, postalServerUrl: string, forceReverify = false ): Promise { - const domainInfo = await postalDB.query.domains.findFirst({ - where: eq(domains.uuid, domainId) - }); - - if (!domainInfo) { - return { error: 'Domain not found, Contact Support', errorCode: 3 }; - } + return tracer.startActiveSpan('Get Domain DNS Records', async (span) => { + const domainInfo = await postalDB.query.domains.findFirst({ + where: eq(domains.uuid, domainId) + }); - const records: GetDomainDNSRecordsOutput = { - verification: { - valid: false, - name: '', - value: '' - }, - mx: { - valid: false, - name: '', - priority: 0, - value: '' - }, - spf: { - valid: false, - name: '', - value: '', - extraSenders: false - }, - dkim: { - valid: false, - name: '', - value: '' - }, - returnPath: { - valid: false, - name: '', - value: '' - }, - dmarc: { - policy: null, - name: '', - optimal: '', - acceptable: '' + if (!domainInfo) { + return { error: 'Domain not found, Contact Support', errorCode: 3 }; } - }; - const txtRecords = await lookupTXT(domainInfo.name); + span?.setAttributes(flatten({ 'domain.info': domainInfo })); - if (txtRecords.success === false && txtRecords.code !== 0) { - return { - error: `${txtRecords.error} Please retry after sometime, if the problem persists contact Support`, - errorCode: txtRecords.code + const records: GetDomainDNSRecordsOutput = { + verification: { + valid: false, + name: '', + value: '' + }, + mx: { + valid: false, + name: '', + priority: 0, + value: '' + }, + spf: { + valid: false, + name: '', + value: '', + extraSenders: false + }, + dkim: { + valid: false, + name: '', + value: '' + }, + returnPath: { + valid: false, + name: '', + value: '' + }, + dmarc: { + policy: null, + name: '', + optimal: '', + acceptable: '' + } }; - } - - const verificationRecordValue = `${domainInfo.verificationToken}`; - const verificationTxtRecordName = await lookupTXT( - `_unplatform-challenge.${domainInfo.name}` - ); - const verified = verificationTxtRecordName.success - ? verificationTxtRecordName.data.includes(verificationRecordValue) - : false; - - records.verification = { - valid: verified, - name: `_unplatform-challenge`, - value: verificationRecordValue - }; - - if (!verified || !domainInfo.verifiedAt || forceReverify) { - await postalDB - .update(domains) - .set({ - verifiedAt: verified ? sql`CURRENT_TIMESTAMP` : sql`NULL` - }) - .where(eq(domains.uuid, domainId)); - } - const spfDomains = txtRecords.success - ? parseSpfIncludes( - txtRecords.data.find((_) => _.startsWith('v=spf1')) ?? '' - ) - : null; - records.spf.name = '@'; - records.spf.extraSenders = - (spfDomains && - spfDomains.includes.filter((x) => x !== `_spf.${dnsRootUrl}`).length > - 0) ?? - false; - - // We need to resolve duplicate entries incase the spf record is already included, so that we don't have duplicate entries - const allSenders = Array.from( - new Set([ - `_spf.${dnsRootUrl}`, - ...(records.spf.extraSenders ? (spfDomains?.includes ?? []) : []) - ]).values() - ); + const txtRecords = await lookupTXT(domainInfo.name); - records.spf.value = buildSpfRecord(allSenders, '~all'); - records.spf.valid = - spfDomains?.includes.includes(`_spf.${dnsRootUrl}`) ?? false; + if (txtRecords.success === false && txtRecords.code !== 0) { + span?.setAttributes(flatten({ 'dns.error': txtRecords.error })); + return { + error: `${txtRecords.error} Please retry after sometime, if the problem persists contact Support`, + errorCode: txtRecords.code + }; + } - if (!records.spf.valid || domainInfo.spfStatus !== 'OK' || forceReverify) { - await postalDB - .update(domains) - .set( - !spfDomains - ? { spfStatus: 'Missing', spfError: 'SPF record not found' } - : !spfDomains.includes.includes(`_spf.${dnsRootUrl}`) - ? { spfStatus: 'Invalid', spfError: 'SPF record Invalid' } - : { spfStatus: 'OK', spfError: null } - ) - .where(eq(domains.uuid, domainId)); - } + const verificationRecordValue = `${domainInfo.verificationToken}`; + const verificationTxtRecordName = await lookupTXT( + `_unplatform-challenge.${domainInfo.name}` + ); + const verified = verificationTxtRecordName.success + ? verificationTxtRecordName.data.includes(verificationRecordValue) + : false; + + records.verification = { + valid: verified, + name: `_unplatform-challenge`, + value: verificationRecordValue + }; - const publicKey = generatePublicKey(domainInfo.dkimPrivateKey); - records.dkim.name = `unplatform-${domainInfo.dkimIdentifierString}._domainkey`; - records.dkim.value = buildDkimRecord({ - t: 's', - h: 'sha256', - p: publicKey - }); - // We assume these are valid already, if the db says they are not or forceReverify is used, valid gets updated - records.dkim.valid = true; + if (!verified || !domainInfo.verifiedAt || forceReverify) { + await postalDB + .update(domains) + .set({ + verifiedAt: verified ? sql`CURRENT_TIMESTAMP` : sql`NULL` + }) + .where(eq(domains.uuid, domainId)); + } - if (domainInfo.dkimStatus !== 'OK' || forceReverify) { - const domainKeyRecords = await lookupTXT( - `unplatform-${domainInfo.dkimIdentifierString}._domainkey.${domainInfo.name}` + const spfDomains = txtRecords.success + ? parseSpfIncludes( + txtRecords.data.find((_) => _.startsWith('v=spf1')) ?? '' + ) + : null; + records.spf.name = '@'; + records.spf.extraSenders = + (spfDomains && + spfDomains.includes.filter((x) => x !== `_spf.${dnsRootUrl}`).length > + 0) ?? + false; + + // We need to resolve duplicate entries incase the spf record is already included, so that we don't have duplicate entries + const allSenders = Array.from( + new Set([ + `_spf.${dnsRootUrl}`, + ...(records.spf.extraSenders ? (spfDomains?.includes ?? []) : []) + ]).values() ); - if (!domainKeyRecords.success) { - records.dkim.valid = false; + records.spf.value = buildSpfRecord(allSenders, '~all'); + records.spf.valid = + spfDomains?.includes.includes(`_spf.${dnsRootUrl}`) ?? false; + + if (!records.spf.valid || domainInfo.spfStatus !== 'OK' || forceReverify) { await postalDB .update(domains) - .set({ dkimStatus: 'Missing', dkimError: 'DKIM record not found' }) + .set( + !spfDomains + ? { spfStatus: 'Missing', spfError: 'SPF record not found' } + : !spfDomains.includes.includes(`_spf.${dnsRootUrl}`) + ? { spfStatus: 'Invalid', spfError: 'SPF record Invalid' } + : { spfStatus: 'OK', spfError: null } + ) .where(eq(domains.uuid, domainId)); - } else { - const domainKey = parseDkim( - domainKeyRecords.data.find((_) => _.startsWith('v=DKIM1')) ?? '' + } + + const publicKey = generatePublicKey(domainInfo.dkimPrivateKey); + records.dkim.name = `unplatform-${domainInfo.dkimIdentifierString}._domainkey`; + records.dkim.value = buildDkimRecord({ + t: 's', + h: 'sha256', + p: publicKey + }); + // We assume these are valid already, if the db says they are not or forceReverify is used, valid gets updated + records.dkim.valid = true; + + if (domainInfo.dkimStatus !== 'OK' || forceReverify) { + const domainKeyRecords = await lookupTXT( + `unplatform-${domainInfo.dkimIdentifierString}._domainkey.${domainInfo.name}` ); - if (!domainKey || domainKey.h !== 'sha256' || domainKey.p !== publicKey) { + + if (!domainKeyRecords.success) { records.dkim.valid = false; await postalDB .update(domains) - .set({ dkimStatus: 'Invalid', dkimError: 'DKIM record Invalid' }) + .set({ dkimStatus: 'Missing', dkimError: 'DKIM record not found' }) .where(eq(domains.uuid, domainId)); } else { - records.dkim.valid = true; - await postalDB - .update(domains) - .set({ dkimStatus: 'OK', dkimError: null }) - .where(eq(domains.uuid, domainId)); + const domainKey = parseDkim( + domainKeyRecords.data.find((_) => _.startsWith('v=DKIM1')) ?? '' + ); + if ( + !domainKey || + domainKey.h !== 'sha256' || + domainKey.p !== publicKey + ) { + records.dkim.valid = false; + await postalDB + .update(domains) + .set({ dkimStatus: 'Invalid', dkimError: 'DKIM record Invalid' }) + .where(eq(domains.uuid, domainId)); + } else { + records.dkim.valid = true; + await postalDB + .update(domains) + .set({ dkimStatus: 'OK', dkimError: null }) + .where(eq(domains.uuid, domainId)); + } } } - } - records.returnPath.name = `unrp`; - records.returnPath.value = `rp.${postalServerUrl}`; - records.returnPath.valid = true; + records.returnPath.name = `unrp`; + records.returnPath.value = `rp.${postalServerUrl}`; + records.returnPath.valid = true; - if (domainInfo.returnPathStatus !== 'OK' || forceReverify) { - const returnPathCname = await lookupCNAME(`unrp.${domainInfo.name}`); - if (!returnPathCname.success) { - records.returnPath.valid = false; - await postalDB - .update(domains) - .set({ - returnPathStatus: 'Missing', - returnPathError: 'Return-Path CNAME record not found' - }) - .where(eq(domains.uuid, domainId)); - } else if (!returnPathCname.data.includes(records.returnPath.value)) { - records.returnPath.valid = false; - await postalDB - .update(domains) - .set({ - returnPathStatus: 'Invalid', - returnPathError: null - }) - .where(eq(domains.uuid, domainId)); - } else { - await postalDB - .update(domains) - .set({ returnPathStatus: 'OK', returnPathError: null }) - .where(eq(domains.uuid, domainId)); + if (domainInfo.returnPathStatus !== 'OK' || forceReverify) { + const returnPathCname = await lookupCNAME(`unrp.${domainInfo.name}`); + if (!returnPathCname.success) { + records.returnPath.valid = false; + await postalDB + .update(domains) + .set({ + returnPathStatus: 'Missing', + returnPathError: 'Return-Path CNAME record not found' + }) + .where(eq(domains.uuid, domainId)); + } else if (!returnPathCname.data.includes(records.returnPath.value)) { + records.returnPath.valid = false; + await postalDB + .update(domains) + .set({ + returnPathStatus: 'Invalid', + returnPathError: null + }) + .where(eq(domains.uuid, domainId)); + } else { + await postalDB + .update(domains) + .set({ returnPathStatus: 'OK', returnPathError: null }) + .where(eq(domains.uuid, domainId)); + } } - } - records.mx.name = domainInfo.name; - records.mx.priority = 1; - records.mx.value = `mx.${postalServerUrl}`; - records.mx.valid = true; - - if (domainInfo.mxStatus !== 'OK' || forceReverify) { - const mxRecords = await lookupMX(domainInfo.name); - if (!mxRecords.success) { - records.mx.valid = false; - await postalDB - .update(domains) - .set({ mxStatus: 'Missing', mxError: 'MX record not found' }) - .where(eq(domains.uuid, domainId)); - } else if ( - mxRecords.data.length > 1 || - !mxRecords.data.find( - (x) => x.exchange === records.mx.value && x.priority === 1 - ) - ) { - records.mx.valid = false; - await postalDB - .update(domains) - .set({ mxStatus: 'Invalid', mxError: 'MX record Invalid' }) - .where(eq(domains.uuid, domainId)); - } else { - await postalDB - .update(domains) - .set({ mxStatus: 'OK', mxError: null }) - .where(eq(domains.uuid, domainId)); + records.mx.name = domainInfo.name; + records.mx.priority = 1; + records.mx.value = `mx.${postalServerUrl}`; + records.mx.valid = true; + + if (domainInfo.mxStatus !== 'OK' || forceReverify) { + const mxRecords = await lookupMX(domainInfo.name); + if (!mxRecords.success) { + records.mx.valid = false; + await postalDB + .update(domains) + .set({ mxStatus: 'Missing', mxError: 'MX record not found' }) + .where(eq(domains.uuid, domainId)); + } else if ( + mxRecords.data.length > 1 || + !mxRecords.data.find( + (x) => x.exchange === records.mx.value && x.priority === 1 + ) + ) { + records.mx.valid = false; + await postalDB + .update(domains) + .set({ mxStatus: 'Invalid', mxError: 'MX record Invalid' }) + .where(eq(domains.uuid, domainId)); + } else { + await postalDB + .update(domains) + .set({ mxStatus: 'OK', mxError: null }) + .where(eq(domains.uuid, domainId)); + } } - } - const dmarcRecord = await lookupTXT(`_dmarc.${domainInfo.name}`); - if (dmarcRecord.success && dmarcRecord.data.length > 0) { - const dmarcValues = parseDmarc( - dmarcRecord.data.find((_) => _.startsWith('v=DMARC1')) ?? '' - ); - if (dmarcValues) { - records.dmarc.policy = - (dmarcValues.p as 'reject' | 'quarantine' | 'none') || null; + const dmarcRecord = await lookupTXT(`_dmarc.${domainInfo.name}`); + if (dmarcRecord.success && dmarcRecord.data.length > 0) { + const dmarcValues = parseDmarc( + dmarcRecord.data.find((_) => _.startsWith('v=DMARC1')) ?? '' + ); + if (dmarcValues) { + records.dmarc.policy = + (dmarcValues.p as 'reject' | 'quarantine' | 'none') || null; + } } - } - records.dmarc.name = '_dmarc'; - records.dmarc.optimal = buildDmarcRecord({ p: 'reject' }); - records.dmarc.acceptable = buildDmarcRecord({ p: 'quarantine' }); - return records; + records.dmarc.name = '_dmarc'; + records.dmarc.optimal = buildDmarcRecord({ p: 'reject' }); + records.dmarc.acceptable = buildDmarcRecord({ p: 'quarantine' }); + + span?.setAttributes(flatten({ 'dns.records': records })); + return records; + }); } export type SetMailServerKeyInput = { diff --git a/apps/platform/utils/updateDnsRecords.ts b/apps/platform/utils/updateDnsRecords.ts index 80a610ae..570b2039 100644 --- a/apps/platform/utils/updateDnsRecords.ts +++ b/apps/platform/utils/updateDnsRecords.ts @@ -1,12 +1,16 @@ import { domains, orgMembers } from '@u22n/database/schema'; import { mailBridgeTrpcClient } from './tRPCServerClients'; import type { TypeId } from '@u22n/utils/typeid'; +import { getTracer } from '@u22n/otel/helpers'; import { and, eq } from '@u22n/database/orm'; +import { flatten } from '@u22n/otel/exports'; import type { DBType } from '@u22n/database'; import { TRPCError } from '@trpc/server'; import { realtime } from './realtime'; import { ms } from '@u22n/utils/ms'; +const tracer = getTracer('platform/update-dns-records'); + export async function updateDnsRecords( { domainPublicId, @@ -17,235 +21,250 @@ export async function updateDnsRecords( }, db: DBType ) { - const domainResponse = await db.query.domains.findFirst({ - where: and(eq(domains.publicId, domainPublicId), eq(domains.orgId, orgId)), - columns: { - id: true, - domain: true, - disabled: true, - dkimKey: true, - dkimValue: true, - postalHost: true, - postalId: true, - lastDnsCheckAt: true, - sendingMode: true, - receivingMode: true, - domainStatus: true, - forwardingAddress: true, - createdAt: true, - verifiedAt: true, - verificationToken: true - } - }); - - if (!domainResponse) { - throw new TRPCError({ - code: 'NOT_FOUND', - message: 'Domain not found' + return tracer.startActiveSpan('Update DNS Records', async (span) => { + const domainResponse = await db.query.domains.findFirst({ + where: and( + eq(domains.publicId, domainPublicId), + eq(domains.orgId, orgId) + ), + columns: { + id: true, + domain: true, + disabled: true, + dkimKey: true, + dkimValue: true, + postalHost: true, + postalId: true, + lastDnsCheckAt: true, + sendingMode: true, + receivingMode: true, + domainStatus: true, + forwardingAddress: true, + createdAt: true, + verifiedAt: true, + verificationToken: true + } }); - } - if (domainResponse.disabled) { - // if domain is manually disabled, update the DNS status if needed - if ( - ![ - domainResponse.domainStatus, - domainResponse.sendingMode, - domainResponse.receivingMode - ].every((_) => _ === 'disabled') - ) { - await db - .update(domains) - .set({ - receivingMode: 'disabled', - sendingMode: 'disabled', - domainStatus: 'disabled' - }) - .where(eq(domains.id, domainResponse.id)); + if (!domainResponse) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: 'Domain not found' + }); } - throw new TRPCError({ - code: 'FORBIDDEN', - message: 'Domain is disabled' - }); - } - - if ( - !domainResponse.dkimKey || - !domainResponse.dkimValue || - !domainResponse.postalId - ) { - throw new TRPCError({ - code: 'UNPROCESSABLE_CONTENT', - message: 'Domain is not setup properly. Contact support for help' - }); - } - - let { - domainStatus, - sendingMode: domainSendingMode, - receivingMode: domainReceivingMode, - verifiedAt - } = domainResponse; - - const currentDNSRecords = - await mailBridgeTrpcClient.postal.domains.refreshDomainDns.query({ - postalDomainId: domainResponse.postalId, - postalServerUrl: domainResponse.postalHost - }); + span?.setAttributes(flatten({ 'domain.info': domainResponse })); - if ('error' in currentDNSRecords) { - if (currentDNSRecords.errorCode === 3) { - // Domain does not exist, disable the domain - await db - .update(domains) - .set({ - disabled: true, - disabledAt: new Date(), - lastDnsCheckAt: new Date() - }) - .where(eq(domains.id, domainResponse.id)); - } - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message: currentDNSRecords.error - }); - } - - const dnsStatus = { - mxDnsValid: currentDNSRecords.mx.valid, - dkimDnsValid: currentDNSRecords.dkim.valid, - spfDnsValid: currentDNSRecords.spf.valid, - returnPathDnsValid: currentDNSRecords.returnPath.valid, - verification: currentDNSRecords.verification.valid, - dmarcPolicy: currentDNSRecords.dmarc.policy - }; - - // check if domain is verified - if (currentDNSRecords.verification.valid) { - // if domain is verified, update the status and verifiedAt - if (domainStatus === 'unverified') domainStatus = 'pending'; - verifiedAt = new Date(); - } else { - // if domain is not verified, check if it was verified before - if (verifiedAt) { - // if verifiedAt is older than 7 days, set the domain to disabled - if (Date.now() - verifiedAt.getTime() > ms('7 days')) { + if (domainResponse.disabled) { + // if domain is manually disabled, update the DNS status if needed + if ( + ![ + domainResponse.domainStatus, + domainResponse.sendingMode, + domainResponse.receivingMode + ].every((_) => _ === 'disabled') + ) { await db .update(domains) .set({ - domainStatus: 'disabled', receivingMode: 'disabled', sendingMode: 'disabled', - lastDnsCheckAt: new Date(), - disabledAt: new Date() + domainStatus: 'disabled' }) .where(eq(domains.id, domainResponse.id)); - return { - error: 'Your Domain has been Disabled due to Incorrect verification', - dnsRecords: currentDNSRecords - }; - // if verifiedAt is newer than 7 days, set the domain to unverified - } else { - domainStatus = 'unverified'; } - // if domain was never verified, check if 3 days have passed since creation - } else { - // if domain is older than 3 days, set the domain to disabled - if (Date.now() - domainResponse.createdAt.getTime() > ms('3 days')) { - await db - .update(domains) - .set({ - domainStatus: 'disabled', - receivingMode: 'disabled', - sendingMode: 'disabled', - lastDnsCheckAt: new Date(), - disabledAt: new Date() - }) - .where(eq(domains.id, domainResponse.id)); - return { - error: 'Your Domain has been Disabled', - dnsRecords: currentDNSRecords - }; - // if domain is newer than 3 days, set the domain to unverified - } else { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'Domain is disabled' + }); + } + + if ( + !domainResponse.dkimKey || + !domainResponse.dkimValue || + !domainResponse.postalId + ) { + throw new TRPCError({ + code: 'UNPROCESSABLE_CONTENT', + message: 'Domain is not setup properly. Contact support for help' + }); + } + + let { + domainStatus, + sendingMode: domainSendingMode, + receivingMode: domainReceivingMode, + verifiedAt + } = domainResponse; + + const currentDNSRecords = + await mailBridgeTrpcClient.postal.domains.refreshDomainDns.query({ + postalDomainId: domainResponse.postalId, + postalServerUrl: domainResponse.postalHost + }); + + span?.setAttributes(flatten({ 'dns.results': currentDNSRecords })); + + if ('error' in currentDNSRecords) { + if (currentDNSRecords.errorCode === 3) { + // Domain does not exist, disable the domain await db .update(domains) .set({ - lastDnsCheckAt: new Date(), - domainStatus: 'unverified' + disabled: true, + disabledAt: new Date(), + lastDnsCheckAt: new Date() }) .where(eq(domains.id, domainResponse.id)); - return { - error: 'Your Domain is not verified', - dnsRecords: currentDNSRecords - }; } + throw new TRPCError({ + code: 'INTERNAL_SERVER_ERROR', + message: currentDNSRecords.error + }); } - } - - // Check if Sending mode is valid - const sendingModeValid = - dnsStatus.spfDnsValid && - dnsStatus.dkimDnsValid && - dnsStatus.returnPathDnsValid; - - domainSendingMode = sendingModeValid ? 'native' : 'disabled'; - - // Check if Receiving mode is valid - const receivingModeValid = dnsStatus.mxDnsValid; - - if (receivingModeValid) { - domainReceivingMode = 'native'; - } else if (domainReceivingMode !== 'disabled') { - domainReceivingMode = 'forwarding'; - } else { - domainReceivingMode = 'disabled'; - } - - // if either of the modes are valid, set the domain to active if it is pending - if (domainStatus === 'pending' && (sendingModeValid || receivingModeValid)) { - domainStatus = 'active'; - } - - await db - .update(domains) - .set({ - mxDnsValid: dnsStatus.mxDnsValid, - dkimDnsValid: dnsStatus.dkimDnsValid, - spfDnsValid: dnsStatus.spfDnsValid, - returnPathDnsValid: dnsStatus.returnPathDnsValid, - receivingMode: domainReceivingMode, - sendingMode: domainSendingMode, - lastDnsCheckAt: new Date(), - domainStatus, - verifiedAt - }) - .where(eq(domains.id, domainResponse.id)); - // If any record is not valid, send an alert to the admins - if (domainStatus !== 'active' || sendingModeValid || receivingModeValid) { - const orgAdmins = await db.query.orgMembers.findMany({ - where: and(eq(orgMembers.orgId, orgId), eq(orgMembers.role, 'admin')), - columns: { - publicId: true + const dnsStatus = { + mxDnsValid: currentDNSRecords.mx.valid, + dkimDnsValid: currentDNSRecords.dkim.valid, + spfDnsValid: currentDNSRecords.spf.valid, + returnPathDnsValid: currentDNSRecords.returnPath.valid, + verification: currentDNSRecords.verification.valid, + dmarcPolicy: currentDNSRecords.dmarc.policy + }; + + // check if domain is verified + if (currentDNSRecords.verification.valid) { + // if domain is verified, update the status and verifiedAt + if (domainStatus === 'unverified') domainStatus = 'pending'; + verifiedAt = new Date(); + } else { + // if domain is not verified, check if it was verified before + if (verifiedAt) { + // if verifiedAt is older than 7 days, set the domain to disabled + if (Date.now() - verifiedAt.getTime() > ms('7 days')) { + await db + .update(domains) + .set({ + domainStatus: 'disabled', + receivingMode: 'disabled', + sendingMode: 'disabled', + lastDnsCheckAt: new Date(), + disabledAt: new Date() + }) + .where(eq(domains.id, domainResponse.id)); + return { + error: + 'Your Domain has been Disabled due to Incorrect verification', + dnsRecords: currentDNSRecords + }; + // if verifiedAt is newer than 7 days, set the domain to unverified + } else { + domainStatus = 'unverified'; + } + // if domain was never verified, check if 3 days have passed since creation + } else { + // if domain is older than 3 days, set the domain to disabled + if (Date.now() - domainResponse.createdAt.getTime() > ms('3 days')) { + await db + .update(domains) + .set({ + domainStatus: 'disabled', + receivingMode: 'disabled', + sendingMode: 'disabled', + lastDnsCheckAt: new Date(), + disabledAt: new Date() + }) + .where(eq(domains.id, domainResponse.id)); + + return { + error: 'Your Domain has been Disabled', + dnsRecords: currentDNSRecords + }; + // if domain is newer than 3 days, set the domain to unverified + } else { + await db + .update(domains) + .set({ + lastDnsCheckAt: new Date(), + domainStatus: 'unverified' + }) + .where(eq(domains.id, domainResponse.id)); + return { + error: 'Your Domain is not verified', + dnsRecords: currentDNSRecords + }; + } } - }); - await realtime.emit({ - event: 'admin:issue:refresh', - data: {}, - orgMemberPublicIds: orgAdmins.map((_) => _.publicId) - }); - } - - return { - dnsStatus: dnsStatus, - dnsRecords: currentDNSRecords, - domainStatus: domainStatus, - domainSendingMode: domainSendingMode, - domainReceivingMode: domainReceivingMode, - forwardingAddress: domainResponse.forwardingAddress, - checked: new Date() - }; + } + + // Check if Sending mode is valid + const sendingModeValid = + dnsStatus.spfDnsValid && + dnsStatus.dkimDnsValid && + dnsStatus.returnPathDnsValid; + + domainSendingMode = sendingModeValid ? 'native' : 'disabled'; + + // Check if Receiving mode is valid + const receivingModeValid = dnsStatus.mxDnsValid; + + if (receivingModeValid) { + domainReceivingMode = 'native'; + } else if (domainReceivingMode !== 'disabled') { + domainReceivingMode = 'forwarding'; + } else { + domainReceivingMode = 'disabled'; + } + + // if either of the modes are valid, set the domain to active if it is pending + if ( + domainStatus === 'pending' && + (sendingModeValid || receivingModeValid) + ) { + domainStatus = 'active'; + } + + await db + .update(domains) + .set({ + mxDnsValid: dnsStatus.mxDnsValid, + dkimDnsValid: dnsStatus.dkimDnsValid, + spfDnsValid: dnsStatus.spfDnsValid, + returnPathDnsValid: dnsStatus.returnPathDnsValid, + receivingMode: domainReceivingMode, + sendingMode: domainSendingMode, + lastDnsCheckAt: new Date(), + domainStatus, + verifiedAt + }) + .where(eq(domains.id, domainResponse.id)); + + span?.setAttributes(flatten({ 'dns.status': dnsStatus })); + + // If any record is not valid, send an alert to the admins + if (domainStatus !== 'active' || sendingModeValid || receivingModeValid) { + const orgAdmins = await db.query.orgMembers.findMany({ + where: and(eq(orgMembers.orgId, orgId), eq(orgMembers.role, 'admin')), + columns: { + publicId: true + } + }); + await realtime.emit({ + event: 'admin:issue:refresh', + data: {}, + orgMemberPublicIds: orgAdmins.map((_) => _.publicId) + }); + } + + return { + dnsStatus: dnsStatus, + dnsRecords: currentDNSRecords, + domainStatus: domainStatus, + domainSendingMode: domainSendingMode, + domainReceivingMode: domainReceivingMode, + forwardingAddress: domainResponse.forwardingAddress, + checked: new Date() + }; + }); } diff --git a/apps/worker/functions/check-dns.ts b/apps/worker/functions/check-dns.ts index 55d5468a..1cefd1e5 100644 --- a/apps/worker/functions/check-dns.ts +++ b/apps/worker/functions/check-dns.ts @@ -1,80 +1,92 @@ import type { TypeId } from '@u22n/utils/typeid'; import { domains } from '@u22n/database/schema'; +import { getTracer } from '@u22n/otel/helpers'; import { discord } from '@u22n/utils/discord'; import { dnsVerifier } from '@u22n/utils/dns'; +import { flatten } from '@u22n/otel/exports'; import { eq } from '@u22n/database/orm'; import { db } from '@u22n/database'; import { env } from '../env'; +const tracer = getTracer('worker/dns-check-function'); + export async function checkDns(publicId: TypeId<'domains'>) { - const now = performance.now(); - const domainInfo = await db.query.domains.findFirst({ - where: eq(domains.publicId, publicId) - }); + return tracer.startActiveSpan('DNS Check', async (span) => { + const now = performance.now(); + const domainInfo = await db.query.domains.findFirst({ + where: eq(domains.publicId, publicId) + }); + + if (!domainInfo) { + throw new Error('Domain not found'); + } - if (!domainInfo) { - throw new Error('Domain not found'); - } + span?.setAttributes(flatten({ 'domain.info': domainInfo })); - const dnsResults = await dnsVerifier({ - rootDomain: domainInfo.domain, - expected: { - verification: { - value: domainInfo.verificationToken - }, - spf: { - includes: [`_spf.${env.PRIMARY_DOMAIN}`] - }, - dkim: { - key: domainInfo.dkimKey, - value: domainInfo.dkimValue - }, - mx: { - exchange: `mx.${domainInfo.postalHost}`, - priority: 1 - }, - returnPath: { - value: `rp.${domainInfo.postalHost}` + const dnsResults = await dnsVerifier({ + rootDomain: domainInfo.domain, + expected: { + verification: { + value: domainInfo.verificationToken + }, + spf: { + includes: [`_spf.${env.PRIMARY_DOMAIN}`] + }, + dkim: { + key: domainInfo.dkimKey, + value: domainInfo.dkimValue + }, + mx: { + exchange: `mx.${domainInfo.postalHost}`, + priority: 1 + }, + returnPath: { + value: `rp.${domainInfo.postalHost}` + } } - } - }); + }); - const hasRecordsChanged = - !!domainInfo.verifiedAt !== dnsResults.verification.valid || - domainInfo.spfDnsValid !== dnsResults.spf.valid || - domainInfo.dkimDnsValid !== dnsResults.dkim.valid || - domainInfo.mxDnsValid !== dnsResults.mx.valid || - domainInfo.returnPathDnsValid !== dnsResults.returnPath.valid; + span?.setAttributes(flatten({ 'dns.results': dnsResults })); - if (hasRecordsChanged) { - if (!domainInfo.postalId) { - await discord.alert( - `An error occurred while checking DNS records for domain ${domainInfo.domain}. The domain does not have a postalId.` - ); - return; - } + const hasRecordsChanged = + !!domainInfo.verifiedAt !== dnsResults.verification.valid || + domainInfo.spfDnsValid !== dnsResults.spf.valid || + domainInfo.dkimDnsValid !== dnsResults.dkim.valid || + domainInfo.mxDnsValid !== dnsResults.mx.valid || + domainInfo.returnPathDnsValid !== dnsResults.returnPath.valid; - // Trigger a DNS check on Platform which will take care of updating the records - await fetch(`${env.PLATFORM_URL}/services/dns-check`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: env.WORKER_ACCESS_KEY - }, - body: JSON.stringify({ - orgId: domainInfo.orgId, - domainPublicId: domainInfo.publicId - }) - }).then((res) => { - if (!res.ok) { - void discord.alert( - `An error occurred while Communicating with Platform Services for DNS records of Domain ${domainInfo.domain}(${domainInfo.publicId}). The Platform responded with status ${res.status}.` + span?.setAttribute('dns.has_changed', hasRecordsChanged); + + if (hasRecordsChanged) { + if (!domainInfo.postalId) { + await discord.alert( + `An error occurred while checking DNS records for domain ${domainInfo.domain}. The domain does not have a postalId.` ); + return; } - }); - await discord.info( - `Found changes in DNS records for domain ${domainInfo.domain}. Refreshed records. Took ${performance.now() - now}ms.` - ); - } + // Trigger a DNS check on Platform which will take care of updating the records + await fetch(`${env.PLATFORM_URL}/services/dns-check`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: env.WORKER_ACCESS_KEY + }, + body: JSON.stringify({ + orgId: domainInfo.orgId, + domainPublicId: domainInfo.publicId + }) + }).then((res) => { + if (!res.ok) { + void discord.alert( + `An error occurred while Communicating with Platform Services for DNS records of Domain ${domainInfo.domain}(${domainInfo.publicId}). The Platform responded with status ${res.status}.` + ); + } + }); + + await discord.info( + `Found changes in DNS records for domain ${domainInfo.domain}. Refreshed records. Took ${performance.now() - now}ms.` + ); + } + }); } diff --git a/apps/worker/services/dns-check-queue.ts b/apps/worker/services/dns-check-queue.ts index a82c89f1..f41a44d1 100644 --- a/apps/worker/services/dns-check-queue.ts +++ b/apps/worker/services/dns-check-queue.ts @@ -2,6 +2,7 @@ import { createQueue, createWorker } from '../utils/queue-helpers'; import { checkDns } from '../functions/check-dns'; import type { TypeId } from '@u22n/utils/typeid'; import { domains } from '@u22n/database/schema'; +import { getTracer } from '@u22n/otel/helpers'; import { discord } from '@u22n/utils/discord'; import { eq } from '@u22n/database/orm'; import { db } from '@u22n/database'; @@ -17,9 +18,18 @@ const dnsCheckQueue = createQueue(QUEUE_NAME, { defaultJobOptions: { removeOnComplete: true, attempts: 3 } }); +const tracer = getTracer('worker/queue/dns-check'); + export const dnsCheckWorker = createWorker( QUEUE_NAME, - (job) => checkDns(job.data.domainPublicId), + (job) => + tracer.startActiveSpan('DNS Check Job', async (span) => { + span?.setAttributes({ + 'job.id': job.id, + 'job.domain.publicId': job.data.domainPublicId + }); + return checkDns(job.data.domainPublicId); + }), { autorun: false } );