Skip to content

Commit 7d39a58

Browse files
committed
feat: mta-sts support
1 parent cc353ab commit 7d39a58

File tree

10 files changed

+162
-3
lines changed

10 files changed

+162
-3
lines changed

.env.local.example

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
# ************************************ platform ********************************************
1616
############################################################################################
1717
PLATFORM_URL=http://localhost:3300
18+
# Generate by running in a terminal: openssl rand -hex 32
19+
PLATFORM_SECRET=secretsecretsecret
1820

1921

2022

apps/mail-bridge/postal-db/functions.ts

+69-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,23 @@ export type GetDomainDNSRecordsOutput =
119119
optimal: string;
120120
acceptable: string;
121121
};
122+
mtaSts: {
123+
dns: {
124+
valid: boolean;
125+
name: string;
126+
value: string;
127+
};
128+
tls: {
129+
valid: boolean;
130+
name: string;
131+
value: string;
132+
};
133+
policy: {
134+
valid: boolean;
135+
name: string;
136+
value: string;
137+
};
138+
};
122139
}
123140
| { error: string };
124141

@@ -168,6 +185,23 @@ export async function getDomainDNSRecords(
168185
name: '',
169186
optimal: '',
170187
acceptable: ''
188+
},
189+
mtaSts: {
190+
dns: {
191+
valid: false,
192+
name: '',
193+
value: ''
194+
},
195+
tls: {
196+
valid: false,
197+
name: '',
198+
value: ''
199+
},
200+
policy: {
201+
valid: false,
202+
name: '',
203+
value: ''
204+
}
171205
}
172206
};
173207

@@ -317,7 +351,7 @@ export async function getDomainDNSRecords(
317351
}
318352
records.mx.name = domainInfo.name;
319353
records.mx.priority = 1;
320-
records.mx.value = `mx.${postalServerUrl}`;
354+
records.mx.value = `${postalServerUrl}`;
321355
records.mx.valid = true;
322356

323357
if (domainInfo.mxStatus !== 'OK' || forceReverify) {
@@ -360,6 +394,40 @@ export async function getDomainDNSRecords(
360394
records.dmarc.name = '_dmarc';
361395
records.dmarc.optimal = buildDmarcRecord({ p: 'reject' });
362396
records.dmarc.acceptable = buildDmarcRecord({ p: 'quarantine' });
397+
398+
const mtaStsDnsRecord = await lookupTXT(`_mta-sts.${domainInfo.name}`);
399+
records.mtaSts.dns.name = '_mta-sts';
400+
records.mtaSts.dns.value = `v=STSv1; id=${Date.now()}`;
401+
if (mtaStsDnsRecord.success && mtaStsDnsRecord.data.length > 0) {
402+
records.mtaSts.dns.valid =
403+
mtaStsDnsRecord.data.filter(
404+
(_) => _.startsWith('v=STSv1;') && _.includes('id=')
405+
).length === 1;
406+
}
407+
408+
const mtaStsTlsRecord = await lookupTXT(`_smtp._tls.${domainInfo.name}`);
409+
records.mtaSts.tls.name = '_smtp._tls';
410+
records.mtaSts.tls.value = `v=TLSRPTv1; rua=mailto:[email protected]`;
411+
if (mtaStsTlsRecord.success && mtaStsTlsRecord.data.length > 0) {
412+
records.mtaSts.tls.valid =
413+
mtaStsTlsRecord.data.filter(
414+
(_) =>
415+
_.startsWith('v=TLSRPTv1;') &&
416+
_.includes('rua=') &&
417+
_.includes('mailto:[email protected]')
418+
).length === 1;
419+
}
420+
421+
const mtaStsPolicyRecord = await lookupCNAME(`mta-sts.${domainInfo.name}`);
422+
records.mtaSts.policy.name = 'mta-sts';
423+
records.mtaSts.policy.value = `mta-sts.${postalServerUrl}`;
424+
if (
425+
mtaStsPolicyRecord.success &&
426+
mtaStsPolicyRecord.data.includes(records.mtaSts.policy.value)
427+
) {
428+
records.mtaSts.policy.valid = true;
429+
}
430+
363431
return records;
364432
}
365433

apps/mail-bridge/trpc/routers/domainRouter.ts

+17
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,23 @@ export const domainRouter = router({
178178
name: 'localhost',
179179
acceptable: 'v=DMARC1; p=quarantine;',
180180
optimal: 'v=DMARC1; p=reject;'
181+
},
182+
mtaSts: {
183+
dns: {
184+
valid: true,
185+
name: 'localhost',
186+
value: 'v=STSv1; id=123456789'
187+
},
188+
tls: {
189+
valid: true,
190+
name: 'localhost',
191+
value: 'v=TLSRPTv1; rua=mailto:tlsrpt@localhost'
192+
},
193+
policy: {
194+
valid: true,
195+
name: 'localhost',
196+
value: 'mta-sts.localhost'
197+
}
181198
}
182199
} satisfies GetDomainDNSRecordsOutput;
183200
}

apps/platform/nitro.config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ export default defineNitroConfig({
6262
primaryDomain: process.env.PRIMARY_DOMAIN || 'localhost',
6363
mailDomains: mailDomains,
6464
transactionalCredentials: transactionalCredentials,
65+
platform: {
66+
secret: process.env.PLATFORM_SECRET || ''
67+
},
6568
auth: {
6669
baseUrl: process.env.WEBAPP_URL || 'http://localhost:3000',
6770
secret: process.env.WEBAPP_AUTH_SECRET,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useRuntimeConfig } from '#imports';
2+
import { db } from '@u22n/database';
3+
import { eq } from '@u22n/database/orm';
4+
import { domains } from '@u22n/database/schema';
5+
import {
6+
eventHandler,
7+
getQuery,
8+
getRouterParam,
9+
send,
10+
setResponseStatus
11+
} from 'h3';
12+
13+
export default eventHandler(async (event) => {
14+
const secret = getRouterParam(event, 'secret');
15+
if (useRuntimeConfig().platform.secret !== secret) {
16+
setResponseStatus(event, 401);
17+
return send(event, 'Unauthorized');
18+
}
19+
20+
const domain = getQuery(event).domain;
21+
if (!domain || typeof domain !== 'string') {
22+
setResponseStatus(event, 400);
23+
return send(event, 'Bad Request');
24+
}
25+
26+
if (!domain.startsWith('mta-sts.')) {
27+
setResponseStatus(event, 400);
28+
return send(event, 'Bad Request');
29+
}
30+
31+
const rootDomain = domain.replace(/^mta-sts\./, '');
32+
const domainResponse = await db.query.domains.findFirst({
33+
where: eq(domains.domain, rootDomain)
34+
});
35+
36+
if (!domainResponse) {
37+
setResponseStatus(event, 403);
38+
return send(event, 'Forbidden');
39+
}
40+
41+
setResponseStatus(event, 200);
42+
return send(event, 'Ok');
43+
});

apps/platform/trpc/routers/orgRouter/mail/domainsRouter.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,10 @@ export const domainsRouter = router({
299299
spfDnsValid: dnsRecords.spf.valid,
300300
returnPathDnsValid: dnsRecords.returnPath.valid,
301301
verification: dnsRecords.verification.valid,
302-
dmarkPolicy: dnsRecords.dmarc.policy
302+
dmarcPolicy: dnsRecords.dmarc.policy,
303+
mtaStsDns: dnsRecords.mtaSts.dns.valid,
304+
mtaStsTls: dnsRecords.mtaSts.tls.valid,
305+
mtaStsPolicy: dnsRecords.mtaSts.policy.valid
303306
};
304307

305308
// take all dns Records and count how many are valid, if all are valid then allOk
@@ -368,6 +371,9 @@ export const domainsRouter = router({
368371
dkimDnsValid: dnsStatus.dkimDnsValid,
369372
spfDnsValid: dnsStatus.spfDnsValid,
370373
returnPathDnsValid: dnsStatus.returnPathDnsValid,
374+
mtaStsDnsValid: dnsStatus.mtaStsDns,
375+
mtaStsTlsValid: dnsStatus.mtaStsTls,
376+
mtaStsPolicyValid: dnsStatus.mtaStsPolicy,
371377
receivingMode: domainReceivingMode,
372378
sendingMode: domainSendingMode,
373379
lastDnsCheckAt: new Date(),

apps/web-app/pages/[orgShortCode]/settings/org/mail/domains/[domainId].vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152
{
153153
label: 'DMARC-Record',
154154
slot: 'dmarc-record',
155-
status: domainDnsQuery.value?.dnsStatus?.dmarkPolicy || null
155+
status: domainDnsQuery.value?.dnsStatus?.dmarcPolicy || null
156156
}
157157
];
158158
});

packages/caddy-mta-sts/Caddyfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
on_demand_tls {
3+
ask {env.PLATFORM_URL}/caddy-check/{env.PLATFORM_SECRET}
4+
}
5+
}
6+
7+
https:// {
8+
tls {
9+
on_demand
10+
}
11+
root * ./files
12+
file_server
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
version: STSv1
2+
mode: enforce
3+
mx: *.e.uninbox.com
4+
max_age: 86400

packages/database/schema.ts

+3
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ export const domains = mysqlTable(
559559
returnPathDnsValid: boolean('return_path_dns_valid')
560560
.notNull()
561561
.default(false),
562+
mtaStsDnsValid: boolean('mta_sts_dns_valid').notNull().default(false),
563+
mtaStsTlsValid: boolean('mta_sts_tls_valid').notNull().default(false),
564+
mtaStsPolicyValid: boolean('mta_sts_policy_valid').notNull().default(false),
562565
lastDnsCheckAt: timestamp('last_dns_check_at'),
563566
disabledAt: timestamp('disabled_at'),
564567
verifiedAt: timestamp('verified_at'),

0 commit comments

Comments
 (0)