2weekmail/api/utils/cloudflare.js
2025-03-19 19:56:57 -05:00

187 lines
5.8 KiB
JavaScript

const axios = require('axios');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
require('dotenv').config();
const path = require('path');
const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js'));
// Create Cloudflare API client
const _createCloudflareClient = () => {
// If using API Token (preferred method)
if (process.env.CF_API_TOKEN) {
return axios.create({
baseURL: 'https://api.cloudflare.com/client/v4',
headers: {
'Authorization': `Bearer ${process.env.CF_API_TOKEN}`,
'Content-Type': 'application/json'
},
timeout: 10000
});
}
// If using API Key (legacy method)
else if (process.env.CF_API_KEY && process.env.CF_EMAIL) {
return axios.create({
baseURL: 'https://api.cloudflare.com/client/v4',
headers: {
'X-Auth-Key': process.env.CF_API_KEY,
'X-Auth-Email': process.env.CF_EMAIL,
'Content-Type': 'application/json'
},
timeout: 10000
});
} else {
throw new Error('No valid Cloudflare authentication method found');
}
};
async function generateDkimKey(domain) {
try {
// Ensure the domain directory exists in the container
await execPromise(`docker exec mailserver_opendkim mkdir -p /etc/opendkim/keys/${domain}`);
// Generate the DKIM key
await execPromise(`docker exec mailserver_opendkim opendkim-genkey -D /etc/opendkim/keys/${domain} -d ${domain} -s mail`);
// Set proper permissions
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;
if (!dkimValue) {
throw new Error('Could not extract DKIM value from key file');
}
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 = _createCloudflareClient();
try {
// Step 1: Get the zone ID for the domain
console.log(`Fetching zone for domain: ${domain}`);
const zonesResponse = await cf.get('/zones', {
params: {
name: domain
}
});
if (!zonesResponse.data.success) {
console.error('Cloudflare API request failed:', zonesResponse.data.errors);
return false;
}
if (zonesResponse.data.result.length === 0) {
console.error(`Zone not found for domain: ${domain}`);
return false;
}
const zoneId = zonesResponse.data.result[0].id;
console.log(`Found zone ID: ${zoneId} for domain: ${domain}`);
// Step 2: Get existing DNS records
console.log(`Fetching existing DNS records for ${domain}...`);
const recordsResponse = await cf.get(`/zones/${zoneId}/dns_records`);
if (!recordsResponse.data.success) {
console.error('Failed to fetch DNS records:', recordsResponse.data.errors);
return false;
}
// Step 3: Delete existing records (except NS and SOA)
const existingRecords = recordsResponse.data.result;
console.log(`Found ${existingRecords.length} DNS records`);
for (const record of existingRecords) {
if (record.type !== 'NS' && record.type !== 'SOA') {
console.log(`Deleting record: ${record.type} ${record.name}`);
try {
await cf.delete(`/zones/${zoneId}/dns_records/${record.id}`);
} catch (error) {
console.error(`Failed to delete record ${record.id}:`, error.message);
}
}
}
// Step 4: Add MX record
console.log(`Adding MX record for ${domain}`);
await cf.post(`/zones/${zoneId}/dns_records`, {
type: 'MX',
name: '@',
content: `mail.${domain}`,
priority: 10,
ttl: 3600
});
// Step 5: Add SPF record
console.log(`Adding SPF record for ${domain}`);
await cf.post(`/zones/${zoneId}/dns_records`, {
type: 'TXT',
name: '@',
content: `"v=spf1 mx a ip4:${process.env.SERVER_IP} -all"`,
ttl: 3600
});
// Step 6: Generate and add DKIM record
console.log(`Generating DKIM key for ${domain}`);
const dkimRecord = await generateDkimKey(domain);
if (dkimRecord) {
console.log(`Adding DKIM record for ${domain}`);
// Fix the content format - remove any existing quotes and add exactly one set
const rawValue = dkimRecord.value.replace(/"/g, '');
const dkimContent = `"${rawValue}"`;
console.log(`DKIM content: ${dkimContent}`);
await cf.post(`/zones/${zoneId}/dns_records`, {
type: 'TXT',
name: 'mail._domainkey',
content: dkimContent,
ttl: 3600
});
}
// Step 7: Add DMARC record
console.log(`Adding DMARC record for ${domain}`);
await cf.post(`/zones/${zoneId}/dns_records`, {
type: 'TXT',
name: '_dmarc',
content: `"v=DMARC1; p=reject; rua=mailto:postmaster@${domain}"`,
ttl: 3600
});
// Step 8: Add A record for mail subdomain
console.log(`Adding A record for mail.${domain}`);
await cf.post(`/zones/${zoneId}/dns_records`, {
type: 'A',
name: 'mail',
content: process.env.SERVER_IP,
ttl: 3600
});
console.log(`DNS configuration completed for ${domain}`);
return true;
} catch (error) {
console.error(`Error configuring DNS for ${domain}:`, error.message);
if (error.response) {
console.error('API error details:', error.response.data);
}
return false;
}
}
module.exports = {
configureDNS,
generateDkimKey
};