296 lines
8.4 KiB
JavaScript
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();
|