chore: first commit

This commit is contained in:
mahyargdz
2025-06-24 11:26:06 +03:30
commit ccbf743ecb
186 changed files with 22258 additions and 0 deletions
+334
View File
@@ -0,0 +1,334 @@
import * as crypto from "crypto";
import * as dns from "dns";
import { promisify } from "util";
import { EntityManager } from "@mikro-orm/core";
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import { firstValueFrom } from "rxjs";
import { DnsRecordMessage, DomainMessage } from "../../../common/enums/message.enum";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { CreateDnsRecordDto } from "../DTO/create-dns-record.dto";
import { DnsRecord } from "../entities/dns-record.entity";
import { Domain } from "../entities/domain.entity";
import { DNSRecordType, VerificationStatus } from "../enums/domain-status.enum";
import { DnsRecordRepository } from "../repositories/dns-record.repository";
import { DomainRepository } from "../repositories/domain.repository";
@Injectable()
export class DnsService {
private readonly logger = new Logger(DnsService.name);
private readonly mailServerDomain: string = "mail.danakcorp.com";
private readonly spfRecord: string = `v=spf1 include:${this.mailServerDomain} ~all`;
private readonly dmarcRecord: string = `v=DMARC1; p=quarantine; rua=mailto:dmarc@${this.mailServerDomain}; ruf=mailto:dmarc@${this.mailServerDomain}`;
private readonly resolveTxt = promisify(dns.resolveTxt);
private readonly resolveCname = promisify(dns.resolveCname);
private readonly resolveMx = promisify(dns.resolveMx);
constructor(
private readonly dnsRecordRepository: DnsRecordRepository,
private readonly domainRepository: DomainRepository,
private readonly mailServerService: MailServerService,
private readonly em: EntityManager,
) {}
/*******************************/
async createDnsRecord(createDnsRecordDto: CreateDnsRecordDto) {
const domain = await this.domainRepository.findOne({ id: createDnsRecordDto.domainId });
if (!domain) throw new BadRequestException(DomainMessage.DOMAIN_NOT_FOUND);
const dnsRecord = this.dnsRecordRepository.create({
...createDnsRecordDto,
domain,
});
await this.em.persistAndFlush(dnsRecord);
this.logger.log(`DNS record created: ${dnsRecord.name} ${dnsRecord.type}`);
return { dnsRecord };
}
/**
* Get DNS records for domain
*/
async getDomainDnsRecords(domainId: string) {
const dnsRecords = await this.dnsRecordRepository.findByDomainId(domainId);
return { dnsRecords };
}
/**
* Update DNS record
*/
async updateDnsRecord(id: string, updates: Partial<DnsRecord>) {
const dnsRecord = await this.dnsRecordRepository.findOne({ id });
if (!dnsRecord) throw new BadRequestException(DnsRecordMessage.DNS_RECORD_NOT_FOUND);
this.em.assign(dnsRecord, updates);
await this.em.persistAndFlush(dnsRecord);
return { dnsRecord };
}
/*******************************/
async deleteDnsRecord(id: string) {
const dnsRecord = await this.dnsRecordRepository.findOne({ id });
if (!dnsRecord) throw new BadRequestException(DnsRecordMessage.DNS_RECORD_NOT_FOUND);
dnsRecord.isActive = false;
dnsRecord.deletedAt = new Date();
await this.em.persistAndFlush(dnsRecord);
return { message: DnsRecordMessage.DNS_RECORD_DELETED };
}
/*******************************/
async generateRequiredDnsRecords(domain: Domain) {
const requiredRecords: Partial<DnsRecord>[] = [];
// MX Record
requiredRecords.push({
name: domain.name,
type: DNSRecordType.MX,
value: this.mailServerDomain,
priority: 10,
isRequired: true,
description: "Mail exchange record for email delivery",
});
// SPF Record
requiredRecords.push({
name: domain.name,
type: DNSRecordType.SPF,
value: this.spfRecord,
isRequired: true,
description: "Sender Policy Framework record",
});
// DMARC Record
requiredRecords.push({
name: `_dmarc.${domain.name}`,
type: DNSRecordType.DMARC,
value: this.dmarcRecord,
isRequired: true,
description: "DMARC policy record",
});
const dnsRecords: DnsRecord[] = [];
for (const recordData of requiredRecords) {
const dnsRecord = this.dnsRecordRepository.create({
name: recordData.name!,
type: recordData.type!,
value: recordData.value!,
domain,
isRequired: recordData.isRequired ?? true,
description: recordData.description,
priority: recordData.priority,
ttl: recordData.ttl,
});
dnsRecords.push(dnsRecord);
}
await this.em.persistAndFlush(dnsRecords);
this.logger.log(`Generated ${dnsRecords.length} required DNS records for ${domain.name}`);
return { dnsRecords };
}
/**
* Create DKIM record
*/
async createDKIMRecord(domain: Domain, selector: string, dkimName: string, dkimValue: string, wildduckId: string) {
const dkimRecord = this.dnsRecordRepository.create({
name: dkimName,
type: DNSRecordType.DKIM,
value: dkimValue,
domain,
isRequired: true,
description: `DKIM public key for selector ${selector}`,
wildduckId,
});
await this.em.persistAndFlush(dkimRecord);
this.logger.log(`DKIM record created for ${domain.name} with selector ${selector}`);
return { dkimRecord };
}
/**
* Generate DKIM keys
*/
async generateDKIMKeysByMailServer(domainName: string, selector: string) {
const data = await firstValueFrom(this.mailServerService.dkim.createDKIMKey({ domain: domainName, selector }));
return { publicKey: data.publicKey, privateKey: data.dnsTxt.value, dnsTxt: data.dnsTxt, wildduckId: data.id };
}
/**
* Generate DKIM keys native
*/
async generateDKIMKeysByNative() {
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
},
});
// Extract the public key without headers and newlines for DNS
const publicKeyForDNS = publicKey
.replace(/-----BEGIN PUBLIC KEY-----/g, "")
.replace(/-----END PUBLIC KEY-----/g, "")
.replace(/\n/g, "");
return {
privateKey,
publicKey: publicKeyForDNS,
};
}
/**
* Verify domain DNS configuration
*/
async verifyDomainDNS(domain: Domain) {
const dnsRecords = await this.dnsRecordRepository.findByDomainId(domain.id);
const requiredRecords = dnsRecords.filter((record) => record.isRequired && record.isActive);
let allVerified = true;
for (const record of requiredRecords) {
const isVerified = await this.verifyDnsRecord(record);
if (!isVerified) {
allVerified = false;
}
}
return allVerified;
}
/**
* Verify individual DNS record
*/
async verifyDnsRecord(dnsRecord: DnsRecord) {
try {
dnsRecord.verificationAttempts += 1;
dnsRecord.lastCheckedAt = new Date();
let isVerified = false;
switch (dnsRecord.type) {
case DNSRecordType.TXT:
case DNSRecordType.SPF:
case DNSRecordType.DMARC:
case DNSRecordType.DKIM:
isVerified = await this.verifyTxtRecord(dnsRecord);
break;
case DNSRecordType.MX:
isVerified = await this.verifyMxRecord(dnsRecord);
break;
case DNSRecordType.CNAME:
isVerified = await this.verifyCnameRecord(dnsRecord);
break;
default:
this.logger.warn(`DNS record type ${dnsRecord.type} verification not implemented`);
isVerified = false;
}
if (isVerified) {
dnsRecord.status = VerificationStatus.VERIFIED;
dnsRecord.lastVerifiedAt = new Date();
dnsRecord.errorMessage = undefined;
} else {
dnsRecord.status = VerificationStatus.FAILED;
dnsRecord.errorMessage = "DNS record not found or value mismatch";
}
await this.em.persistAndFlush(dnsRecord);
return isVerified;
} catch (error) {
dnsRecord.status = VerificationStatus.FAILED;
dnsRecord.errorMessage = error instanceof Error ? error.message : "Unknown error";
await this.em.persistAndFlush(dnsRecord);
this.logger.error(`DNS verification failed for ${dnsRecord.name}:`, error);
return false;
}
}
/**
* Verify TXT record
*/
private async verifyTxtRecord(dnsRecord: DnsRecord) {
try {
const txtRecords = await this.resolveTxt(dnsRecord.name);
const flatRecords = txtRecords.flat();
return flatRecords.some((record) => record.includes(dnsRecord.value) || record === dnsRecord.value);
} catch (error) {
this.logger.debug(`TXT record verification failed for ${dnsRecord.name}:`, error instanceof Error ? error.message : "Unknown error");
return false;
}
}
/**
* Verify MX record
*/
private async verifyMxRecord(dnsRecord: DnsRecord) {
try {
const mxRecords = await this.resolveMx(dnsRecord.name);
return mxRecords.some((mx) => mx.exchange === dnsRecord.value && mx.priority === (dnsRecord.priority || 10));
} catch (error) {
this.logger.debug(`MX record verification failed for ${dnsRecord.name}:`, error instanceof Error ? error.message : "Unknown error");
return false;
}
}
/**
* Verify CNAME record
*/
private async verifyCnameRecord(dnsRecord: DnsRecord) {
try {
const cnameRecords = await this.resolveCname(dnsRecord.name);
return cnameRecords.includes(dnsRecord.value);
} catch (error) {
this.logger.debug(`CNAME record verification failed for ${dnsRecord.name}:`, error instanceof Error ? error.message : "Unknown error");
return false;
}
}
/**
* Get DNS record statistics
*/
async getDnsRecordStats(domainId: string): Promise<{
total: number;
verified: number;
pending: number;
failed: number;
required: number;
}> {
return this.dnsRecordRepository.getDomainDnsStats(domainId);
}
/**
* Verify all DNS records that need checking
*/
async verifyPendingDnsRecords(): Promise<void> {
const records = await this.dnsRecordRepository.findNeedingVerification();
for (const record of records) {
try {
await this.verifyDnsRecord(record);
} catch (error) {
this.logger.error(`Failed to verify DNS record ${record.name}:`, error);
}
}
}
}