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();