2weekmail/api/controllers/DomainController.js
2025-03-19 19:56:57 -05:00

296 lines
8.4 KiB
JavaScript

const path = require('path');
const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js'));
const BaseController = require(path.resolve(process.env.ROOT_PATH, './controllers/BaseController.js'));
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const CloudflareAPI = require('cloudflare');
class DomainController extends BaseController {
constructor() {
super();
this.protected('getAvailableDomains');
// Admin-only endpoints for managing domains
this.admin('getAllDomains', 'createDomain', 'updateDomain', 'deleteDomain');
}
/**
* @swagger
* /domains/available:
* get:
* summary: Get all available domains
* description: Retrieves a list of all active domains
* tags: [Domains]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: List of available domains
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* domain:
* type: string
* description: Domain name
* description:
* type: string
* description: Domain description
* 401:
* description: Unauthorized - Authentication required
* 500:
* description: Server error
*/
async getAvailableDomains(req, res) {
try {
const domains = await models.Domain.query()
.where('active', 1)
.select(['domain', 'description'])
.orderBy('domain');
return res.json(domains);
} catch (error) {
console.error('Error fetching available domains:', error);
return res.status(500).json({ error: 'Failed to fetch domains' });
}
}
/**
* Get all domains (admin only)
* This returns all domains including inactive ones
*/
async getAllDomains(req, res) {
try {
const domains = await models.Domain.query()
.select(['id', 'domain', 'description', 'active', 'created', 'modified'])
.orderBy('domain');
return res.json(domains);
} catch (error) {
console.error('Error fetching all domains:', error);
return res.status(500).json({ error: 'Failed to fetch domains' });
}
}
/**
* Create a new domain (admin only)
*/
async createDomain(req, res) {
try {
const { domain, description, active } = req.body;
// Validate input
if (!domain) {
return res.status(400).json({ error: 'Domain name is required' });
}
// Check if domain already exists
const existingDomain = await models.Domain.query().where('domain', domain).first();
if (existingDomain) {
return res.status(409).json({ error: 'Domain already exists' });
}
// Create the domain
const newDomain = await models.Domain.query().insert({
domain,
description: description || domain,
active: active !== undefined ? active : 1,
transport: 'smtp',
backupmx: 0,
created: new Date().toISOString().slice(0, 19).replace('T', ' '),
modified: new Date().toISOString().slice(0, 19).replace('T', ' ')
});
const dkimRecord = await generateDkimKey(domain);
return res.status(201).json({
domain: newDomain,
dnsConfiguration: {
mx: {
host: '@',
value: `10 mail.${domain}`
},
spf: {
host: '@',
type: 'TXT',
value: `v=spf1 mx a ip4:${process.env.SERVER_IP} -all`
},
dkim: dkimRecord,
dmarc: {
host: '_dmarc',
type: 'TXT',
value: `v=DMARC1; p=reject; rua=mailto:postmaster@${domain}`
},
a: {
host: 'mail',
value: process.env.SERVER_IP
}
}
});
} catch (error) {
console.error('Error creating domain:', error);
return res.status(500).json({ error: 'Failed to create domain' });
}
}
/**
* Update a domain (admin only)
*/
async updateDomain(req, res) {
try {
const { id } = req.params;
const { description, active } = req.body;
// Check if domain exists
const domain = await models.Domain.query().findById(id);
if (!domain) {
return res.status(404).json({ error: 'Domain not found' });
}
// Update the domain
const updatedDomain = await models.Domain.query().patchAndFetchById(id, {
description: description !== undefined ? description : domain.description,
active: active !== undefined ? active : domain.active,
modified: new Date().toISOString().slice(0, 19).replace('T', ' ')
});
return res.json(updatedDomain);
} catch (error) {
console.error('Error updating domain:', error);
return res.status(500).json({ error: 'Failed to update domain' });
}
}
/**
* Delete a domain (admin only)
*/
async deleteDomain(req, res) {
try {
const { id } = req.params;
// Check if domain exists
const domain = await models.Domain.query().findById(id);
if (!domain) {
return res.status(404).json({ error: 'Domain not found' });
}
// Check if domain has mailboxes
const mailboxCount = await models.Mailbox.query()
.where('domain', domain.domain)
.resultSize();
if (mailboxCount > 0) {
return res.status(409).json({
error: 'Cannot delete domain with active mailboxes',
mailboxCount
});
}
// Delete the domain
await models.Domain.query().deleteById(id);
return res.json({ message: 'Domain deleted successfully' });
} catch (error) {
console.error('Error deleting domain:', error);
return res.status(500).json({ error: 'Failed to delete domain' });
}
}
}
async function generateDkimKey(domain) {
try {
await execPromise(`docker exec mailserver_opendkim opendkim-genkey -D /etc/opendkim/keys/${domain} -d ${domain} -s mail`);
await execPromise(`docker exec mailserver_opendkim chown -R opendkim:opendkim /etc/opendkim/keys/${domain}`);
// Read the generated public key
const { stdout } = await execPromise(`docker exec mailserver_opendkim cat /etc/opendkim/keys/${domain}/mail.txt`);
// Extract the DKIM record value
const dkimMatch = stdout.match(/p=([^)]+)/);
const dkimValue = dkimMatch ? dkimMatch[1] : null;
return {
host: 'mail._domainkey',
value: `v=DKIM1; k=rsa; p=${dkimValue}`
};
} catch (error) {
console.error('Error generating DKIM key:', error);
return null;
}
}
async function configureDNS(domain) {
const cf = new CloudflareAPI({
email: process.env.CF_EMAIL,
key: process.env.CF_API_TOKEN
});
try {
// First, get the zone ID for the domain
const zones = await cf.zones.browse();
const zone = zones.result.find(z => z.name === domain);
if (!zone) {
console.error(`Zone not found for domain: ${domain}`);
return false;
}
const zoneId = zone.id;
// Add MX record
await cf.dnsRecords.add(zoneId, {
type: 'MX',
name: '@',
content: `mail.${domain}`,
priority: 10,
ttl: 3600
});
// Add SPF record
await cf.dnsRecords.add(zoneId, {
type: 'TXT',
name: '@',
content: `v=spf1 mx a ip4:${process.env.SERVER_IP} -all`,
ttl: 3600
});
// Add DKIM record
const dkimRecord = await generateDkimKey(domain);
if (dkimRecord) {
await cf.dnsRecords.add(zoneId, {
type: 'TXT',
name: 'mail._domainkey',
content: dkimRecord.value,
ttl: 3600
});
}
// Add DMARC record
await cf.dnsRecords.add(zoneId, {
type: 'TXT',
name: '_dmarc',
content: `v=DMARC1; p=reject; rua=mailto:postmaster@${domain}`,
ttl: 3600
});
// Add A record for mail subdomain
await cf.dnsRecords.add(zoneId, {
type: 'A',
name: 'mail',
content: process.env.SERVER_IP,
ttl: 3600
});
return true;
} catch (error) {
console.error('Error configuring DNS:', error);
return false;
}
}
module.exports = new DomainController();