Add Email DNS upsert for Stalwart DKIM, MX, and SPF.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
6b8073f998
commit
6f1b13b6dc
@@ -19,6 +19,7 @@ import { ListDomainsDto } from './dto/list-domains.dto';
|
||||
import { ToggleSslDto } from './dto/toggle-ssl.dto';
|
||||
import { UpdateDomainAdminDto } from './dto/update-domain-admin.dto';
|
||||
import { UpdateParkedHostsDto } from './dto/update-parked-hosts.dto';
|
||||
import { ApplyMailDnsDto } from './dto/apply-mail-dns.dto';
|
||||
import { DomainAdminService } from './domain-admin.service';
|
||||
|
||||
@Controller('domains')
|
||||
@@ -71,6 +72,16 @@ export class DomainAdminController {
|
||||
return this.service.setParkedHosts(domainId, dto, user);
|
||||
}
|
||||
|
||||
@Post(':domainId/mail-dns')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
applyMailDns(
|
||||
@Param('domainId') domainId: string,
|
||||
@Body() dto: ApplyMailDnsDto,
|
||||
@CurrentUser() user: AuthUser,
|
||||
) {
|
||||
return this.service.applyMailDns(domainId, dto, user);
|
||||
}
|
||||
|
||||
@Patch(':domainId')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
update(
|
||||
|
||||
@@ -30,7 +30,14 @@ import { ListDomainsDto } from './dto/list-domains.dto';
|
||||
import { ToggleSslDto } from './dto/toggle-ssl.dto';
|
||||
import { UpdateDomainAdminDto } from './dto/update-domain-admin.dto';
|
||||
import { UpdateParkedHostsDto } from './dto/update-parked-hosts.dto';
|
||||
import { isValidParkedHost, parseParkedDnsMap, uniqueParkedAliases, uniqueParkedHosts } from './parked-host.util';
|
||||
import { ApplyMailDnsDto } from './dto/apply-mail-dns.dto';
|
||||
import { parseDkimPaste } from '../mail-dns/mail-dns';
|
||||
import {
|
||||
isValidParkedHost,
|
||||
parseParkedDnsMap,
|
||||
uniqueParkedAliases,
|
||||
uniqueParkedHosts,
|
||||
} from './parked-host.util';
|
||||
import type { ParkedDnsProvider } from './parked-host.util';
|
||||
|
||||
type DomainRow = {
|
||||
@@ -919,6 +926,65 @@ export class DomainAdminService {
|
||||
};
|
||||
}
|
||||
|
||||
async applyMailDns(domainIdRaw: string, dto: ApplyMailDnsDto, actor: AuthUser) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const domainId = BigInt(domainIdRaw);
|
||||
const domain = await this.prisma.domain.findUnique({
|
||||
where: { id: domainId },
|
||||
select: { id: true, host: true },
|
||||
});
|
||||
if (!domain) throw new NotFoundException('Website not found');
|
||||
|
||||
let ed25519;
|
||||
let rsa;
|
||||
try {
|
||||
ed25519 = parseDkimPaste(dto.ed25519Key, 'ed25519');
|
||||
rsa = parseDkimPaste(dto.rsaKey, 'rsa');
|
||||
} catch (err) {
|
||||
throw new BadRequestException(err instanceof Error ? err.message : 'Invalid DKIM key');
|
||||
}
|
||||
if (ed25519.selector === rsa.selector) {
|
||||
throw new BadRequestException('Ed25519 and RSA DKIM selectors must be different.');
|
||||
}
|
||||
|
||||
const provider = dto.dnsProvider === 'cloudflare' ? 'cloudflare' : 'arvan';
|
||||
const host = domain.host.trim().toLowerCase();
|
||||
try {
|
||||
const result =
|
||||
provider === 'cloudflare'
|
||||
? await this.cloudflareDns.ensureMailRecords(host, [ed25519, rsa])
|
||||
: await this.arvanDns.ensureMailRecords(host, [ed25519, rsa]);
|
||||
|
||||
const parts = [
|
||||
result.created.length ? `created ${result.created.join(', ')}` : '',
|
||||
result.updated.length ? `updated ${result.updated.join(', ')}` : '',
|
||||
result.removed.length ? `removed ${result.removed.join(', ')}` : '',
|
||||
].filter(Boolean);
|
||||
|
||||
return {
|
||||
host,
|
||||
dnsProvider: provider,
|
||||
created: result.created,
|
||||
updated: result.updated,
|
||||
skipped: result.skipped,
|
||||
removed: result.removed,
|
||||
message: parts.length
|
||||
? `Mail DNS updated on ${provider === 'cloudflare' ? 'Cloudflare' : 'Arvan'} (${parts.join('; ')}).`
|
||||
: `Mail DNS already up to date on ${provider === 'cloudflare' ? 'Cloudflare' : 'Arvan'}.`,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new ServiceUnavailableException(
|
||||
this.httpErrorMessage(
|
||||
err,
|
||||
provider === 'cloudflare'
|
||||
? 'Cloudflare mail DNS update failed'
|
||||
: 'Arvan mail DNS update failed',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async applyParkedDns(
|
||||
host: string,
|
||||
provider: ParkedDnsProvider,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { IsIn, IsString, MaxLength, MinLength } from 'class-validator';
|
||||
import { PARKED_DNS_PROVIDERS } from '../parked-host.util';
|
||||
|
||||
export class ApplyMailDnsDto {
|
||||
@IsIn(PARKED_DNS_PROVIDERS)
|
||||
dnsProvider!: (typeof PARKED_DNS_PROVIDERS)[number];
|
||||
|
||||
@IsString()
|
||||
@MinLength(20)
|
||||
@MaxLength(8000)
|
||||
ed25519Key!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(20)
|
||||
@MaxLength(8000)
|
||||
rsaKey!: string;
|
||||
}
|
||||
Reference in New Issue
Block a user