Skip to content

Commit d579b55

Browse files
committed
eradicate TXT records from custom domains; adjust functions to expect only CNAME (dns verification) or CNAME and SSL records (territory UI/UX)
1 parent 1643c09 commit d579b55

File tree

8 files changed

+40
-101
lines changed

8 files changed

+40
-101
lines changed

api/resolvers/domain.js

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { validateSchema, customDomainSchema } from '@/lib/validate'
22
import { GqlAuthenticationError, GqlInputError } from '@/lib/error'
3-
import { randomBytes } from 'node:crypto'
43
import { getDomainMapping } from '@/lib/domains'
54
import { SN_ADMIN_IDS } from '@/lib/constants'
65

@@ -69,61 +68,30 @@ export default {
6968
}
7069

7170
const updatedDomain = await models.$transaction(async tx => {
72-
let existingTXT = null
71+
// we're changing the domain name, delete the domain if it exists
7372
if (existing) {
74-
// if on HOLD, we can resume retaining its TXT record
75-
if (existing.status === 'HOLD') {
76-
// clean any existing domain verification job left
77-
await cleanDomainVerificationJobs(existing, tx)
78-
// get the existing TXT record value
79-
existingTXT = existing.records.find(record => record.type === 'TXT')
80-
} else {
81-
// if not on HOLD, we should delete the domain and start over
82-
await tx.domain.delete({ where: { subName } })
83-
}
73+
// delete any existing domain verification job left
74+
await cleanDomainVerificationJobs(existing, tx)
75+
// delete the domain
76+
await tx.domain.delete({ where: { subName } })
8477
}
8578

86-
const domain = await tx.domain.upsert({
87-
where: { subName },
88-
update: initializeDomain,
89-
create: {
79+
const domain = await tx.domain.create({
80+
data: {
9081
...initializeDomain,
9182
sub: { connect: { name: subName } }
9283
}
9384
})
9485

95-
// create the verification records
96-
const verificationRecords = [
97-
{
86+
// create the CNAME verification record
87+
await tx.domainVerificationRecord.create({
88+
data: {
9889
domainId: domain.id,
9990
type: 'CNAME',
10091
recordName: domainName,
10192
recordValue: new URL(process.env.NEXT_PUBLIC_URL).host
102-
},
103-
{
104-
domainId: domain.id,
105-
type: 'TXT',
106-
recordName: '_snverify.' + domainName,
107-
recordValue: existingTXT // if we're resuming from HOLD, use the existing TXT record
108-
? existingTXT.recordValue
109-
: randomBytes(32).toString('base64')
11093
}
111-
]
112-
113-
// create the verification records
114-
for (const record of verificationRecords) {
115-
await tx.domainVerificationRecord.upsert({
116-
where: {
117-
domainId_type_recordName: {
118-
domainId: domain.id,
119-
type: record.type,
120-
recordName: record.recordName
121-
}
122-
},
123-
update: record,
124-
create: record
125-
})
126-
}
94+
})
12795

12896
// create the job to verify the domain in 30 seconds
12997
await tx.$executeRaw`
@@ -163,7 +131,7 @@ export default {
163131
records: async (domain) => {
164132
if (!domain.records) return []
165133

166-
// O(1) lookups by type, simpler checks for CNAME, TXT and ACM validation records.
134+
// O(1) lookups by type, simpler checks for CNAME and ACM validation records
167135
return Object.fromEntries(domain.records.map(record => [record.type, record]))
168136
}
169137
}

api/typeDefs/domain.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export default gql`
3737
3838
type DomainVerificationRecordMap {
3939
CNAME: DomainVerificationRecord
40-
TXT: DomainVerificationRecord
4140
SSL: DomainVerificationRecord
4241
}
4342
@@ -64,7 +63,6 @@ export default gql`
6463
enum DomainVerificationStage {
6564
GENERAL
6665
CNAME
67-
TXT
6866
ACM_REQUEST_CERTIFICATE
6967
ACM_REQUEST_VALIDATION_VALUES
7068
ACM_VALIDATION
@@ -73,7 +71,6 @@ export default gql`
7371
}
7472
7573
enum DomainRecordType {
76-
TXT
7774
CNAME
7875
SSL
7976
}

components/territory-domains.js

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,12 @@ export const DomainProvider = ({ domain: ssrDomain, children }) => {
4141

4242
export const useDomain = () => useContext(DomainContext)
4343

44-
const getDNSStatusBadge = (cnameStatus, txtStatus) => {
45-
if (cnameStatus === 'VERIFIED' && txtStatus === 'VERIFIED') {
46-
return <Badge bg='success'>DNS verified</Badge>
47-
}
48-
return <Badge bg='warning'>DNS pending</Badge>
49-
}
50-
51-
const getSSLStatusBadge = (status) => {
44+
const getStatusBadge = (type, status) => {
5245
switch (status) {
5346
case 'VERIFIED':
54-
return <Badge bg='success'>SSL verified</Badge>
47+
return <Badge bg='success'>{type} verified</Badge>
5548
default:
56-
return <Badge bg='warning'>SSL pending</Badge>
49+
return <Badge bg='warning'>{type} pending</Badge>
5750
}
5851
}
5952

@@ -68,16 +61,18 @@ const DomainLabel = ({ domain, polling }) => {
6861
{status !== 'HOLD'
6962
? (
7063
<>
71-
{getDNSStatusBadge(records?.CNAME?.status, records?.TXT?.status)}
72-
{getSSLStatusBadge(records?.SSL?.status)}
64+
{getStatusBadge('CNAME', records?.CNAME?.status)}
65+
{getStatusBadge('SSL', records?.SSL?.status)}
7366
</>
7467
)
75-
: (<Badge bg='secondary'>HOLD</Badge>)}
76-
{status === 'HOLD' && (
77-
<SubmitButton variant='link' className='p-0'>
78-
<RefreshLine className={styles.refresh} style={{ width: '1rem', height: '1rem' }} />
79-
</SubmitButton>
80-
)}
68+
: (
69+
<>
70+
<Badge bg='secondary'>HOLD</Badge>
71+
<SubmitButton variant='link' className='p-0'>
72+
<RefreshLine className={styles.refresh} style={{ width: '1rem', height: '1rem' }} />
73+
</SubmitButton>
74+
</>
75+
)}
8176
{polling && <Moon className='spin fill-grey' style={{ width: '1rem', height: '1rem' }} />}
8277
</div>
8378
)}
@@ -127,15 +122,12 @@ const DomainGuidelines = ({ domain }) => {
127122

128123
return (
129124
<div className='d-flex'>
130-
{(records?.CNAME?.status === 'PENDING' || records?.TXT?.status === 'PENDING') && (
125+
{records?.CNAME?.status === 'PENDING' && (
131126
<div className='d-flex flex-column gap-2'>
132127
<h5>Step 1: Verify your domain</h5>
133-
<p>Add the following DNS records to verify ownership of your domain:</p>
128+
<p>Add the following DNS record to verify ownership of your domain:</p>
134129
<h6>CNAME</h6>
135130
{dnsRecord({ record: records?.CNAME })}
136-
<hr />
137-
<h6>TXT</h6>
138-
{dnsRecord({ record: records?.TXT })}
139131
</div>
140132
)}
141133
{records?.SSL?.status === 'PENDING' && (

fragments/domains.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ export const DOMAIN_VERIFICATION_RECORD_MAP_FIELDS = gql`
2525
CNAME {
2626
...DomainVerificationRecordFields
2727
}
28-
TXT {
29-
...DomainVerificationRecordFields
30-
}
3128
SSL {
3229
...DomainVerificationRecordFields
3330
}

lib/domain-verification.js

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -92,22 +92,13 @@ export async function verifyDNSRecord (type, recordName, recordValue) {
9292
let domainRecords = null
9393

9494
try {
95-
switch (type) {
96-
case 'TXT':
97-
// TXT Records checking
98-
domainRecords = await resolver.resolveTxt(recordName)
99-
result.valid = domainRecords.flat().join(' ').includes(recordValue)
100-
break
101-
case 'CNAME':
102-
// CNAME Records checking
103-
domainRecords = await resolver.resolveCname(recordName)
104-
result.valid = domainRecords.some(record =>
105-
record.includes(recordValue)
106-
)
107-
break
108-
default:
109-
result.error = `Invalid DNS record type: ${type}`
110-
break
95+
if (type === 'CNAME') {
96+
domainRecords = await resolver.resolveCname(recordName)
97+
result.valid = domainRecords.some(record =>
98+
record.includes(recordValue)
99+
)
100+
} else {
101+
result.error = `Invalid DNS record type: ${type}`
111102
}
112103
} catch (error) {
113104
if (error.code === 'ENODATA' || error.code === 'ENOTFOUND') {

prisma/migrations/20250504202129_custom_domains_base/migration.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
-- CreateEnum
2-
CREATE TYPE "DomainVerificationStage" AS ENUM ('GENERAL', 'CNAME', 'TXT', 'ACM_REQUEST_CERTIFICATE', 'ACM_REQUEST_VALIDATION_VALUES', 'ACM_VALIDATION', 'ELB_ATTACH_CERTIFICATE', 'VERIFICATION_COMPLETE');
2+
CREATE TYPE "DomainVerificationStage" AS ENUM ('GENERAL', 'CNAME', 'ACM_REQUEST_CERTIFICATE', 'ACM_REQUEST_VALIDATION_VALUES', 'ACM_VALIDATION', 'ELB_ATTACH_CERTIFICATE', 'VERIFICATION_COMPLETE');
33

44
-- CreateEnum
5-
CREATE TYPE "DomainRecordType" AS ENUM ('TXT', 'CNAME', 'SSL');
5+
CREATE TYPE "DomainRecordType" AS ENUM ('CNAME', 'SSL');
66

77
-- CreateEnum
88
CREATE TYPE "DomainVerificationStatus" AS ENUM ('PENDING', 'VERIFIED', 'FAILED', 'ACTIVE', 'HOLD');

prisma/schema.prisma

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,6 @@ enum DomainVerificationStatus {
13211321
enum DomainVerificationStage {
13221322
GENERAL
13231323
CNAME
1324-
TXT
13251324
ACM_REQUEST_CERTIFICATE
13261325
ACM_REQUEST_VALIDATION_VALUES
13271326
ACM_VALIDATION
@@ -1330,7 +1329,6 @@ enum DomainVerificationStage {
13301329
}
13311330

13321331
enum DomainRecordType {
1333-
TXT
13341332
CNAME
13351333
SSL
13361334
}

worker/domainVerification.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ async function verifyDomain (domain, models) {
106106
const records = domain.records || []
107107
const recordMap = Object.fromEntries(records.map(record => [record.type, record]))
108108

109-
// verify both CNAME and TXT records
109+
// verify the CNAME record
110110
const dnsVerified = await verifyDNS(domain, models, recordMap)
111111
if (!dnsVerified) return { status, message: 'DNS verification has failed.' }
112112

@@ -142,14 +142,10 @@ async function verifyDomain (domain, models) {
142142
return { status: 'ACTIVE', message: `Domain ${domain.domainName} has been successfully verified` }
143143
}
144144

145-
// verify both CNAME and TXT records
145+
// verify the CNAME record
146146
async function verifyDNS (domain, models, records) {
147-
if (records.CNAME && records.TXT) {
148-
const [cnameResult, txtResult] = await Promise.all([
149-
verifyRecord('CNAME', records.CNAME, domain, models),
150-
verifyRecord('TXT', records.TXT, domain, models)
151-
])
152-
return cnameResult && txtResult
147+
if (records.CNAME) {
148+
return await verifyRecord('CNAME', records.CNAME, domain, models)
153149
}
154150

155151
return false

0 commit comments

Comments
 (0)