diff --git a/src/acme2.js b/src/acme2.js
index badb0537e..e8ca40acd 100644
--- a/src/acme2.js
+++ b/src/acme2.js
@@ -13,6 +13,7 @@ const assert = require('assert'),
BoxError = require('./boxerror.js'),
crypto = require('crypto'),
debug = require('debug')('box:cert/acme2'),
+ dns = require('./dns.js'),
domains = require('./domains.js'),
fs = require('fs'),
os = require('os'),
@@ -400,10 +401,10 @@ Acme2.prototype.prepareDnsChallenge = async function (hostname, domain, authoriz
debug(`prepareDnsChallenge: update ${challengeSubdomain} with ${txtValue}`);
return new Promise((resolve, reject) => {
- domains.upsertDnsRecords(challengeSubdomain, domain, 'TXT', [ `"${txtValue}"` ], function (error) {
+ dns.upsertDnsRecords(challengeSubdomain, domain, 'TXT', [ `"${txtValue}"` ], function (error) {
if (error) return reject(error);
- domains.waitForDnsRecord(challengeSubdomain, domain, 'TXT', txtValue, { times: 200 }, function (error) {
+ dns.waitForDnsRecord(challengeSubdomain, domain, 'TXT', txtValue, { times: 200 }, function (error) {
if (error) return reject(error);
resolve(challenge);
@@ -427,7 +428,7 @@ Acme2.prototype.cleanupDnsChallenge = async function (hostname, domain, challeng
debug(`cleanupDnsChallenge: remove ${challengeSubdomain} with ${txtValue}`);
return new Promise((resolve, reject) => {
- domains.removeDnsRecords(challengeSubdomain, domain, 'TXT', [ `"${txtValue}"` ], function (error) {
+ dns.removeDnsRecords(challengeSubdomain, domain, 'TXT', [ `"${txtValue}"` ], function (error) {
if (error) return reject(error);
resolve(null);
@@ -522,7 +523,7 @@ Acme2.prototype.getCertificate = async function (vhost, domain, paths) {
debug(`getCertificate: start acme flow for ${vhost} from ${this.caDirectory}`);
if (vhost !== domain && this.wildcard) { // bare domain is not part of wildcard SAN
- vhost = domains.makeWildcard(vhost);
+ vhost = dns.makeWildcard(vhost);
debug(`getCertificate: will get wildcard cert for ${vhost}`);
}
diff --git a/src/apps.js b/src/apps.js
index 2a2fc46ea..883aaa7c1 100644
--- a/src/apps.js
+++ b/src/apps.js
@@ -122,8 +122,8 @@ const appdb = require('./appdb.js'),
constants = require('./constants.js'),
database = require('./database.js'),
debug = require('debug')('box:apps'),
+ dns = require('./dns.js'),
docker = require('./docker.js'),
- domaindb = require('./domaindb.js'),
domains = require('./domains.js'),
eventlog = require('./eventlog.js'),
fs = require('fs'),
@@ -150,6 +150,7 @@ const appdb = require('./appdb.js'),
_ = require('underscore');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
+const domainsList = util.callbackify(domains.list);
// validate the port bindings
function validatePortBindings(portBindings, manifest) {
@@ -390,7 +391,7 @@ function getDuplicateErrorDetails(errorMessage, locations, domainObjectMap, port
const { subdomain, domain } = locations[i];
if (match[1] !== `${subdomain}-${domain}`) continue;
- return new BoxError(BoxError.ALREADY_EXISTS, `Domain '${domains.fqdn(subdomain, domainObjectMap[domain])}' is in use`, { subdomain, domain });
+ return new BoxError(BoxError.ALREADY_EXISTS, `Domain '${dns.fqdn(subdomain, domainObjectMap[domain])}' is in use`, { subdomain, domain });
}
}
@@ -465,9 +466,9 @@ function postProcess(app, domainObjectMap) {
}
app.portBindings = result;
app.iconUrl = app.hasIcon || app.hasAppStoreIcon ? `/api/v1/apps/${app.id}/icon` : null;
- app.fqdn = domains.fqdn(app.location, domainObjectMap[app.domain]);
- app.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
- app.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ app.fqdn = dns.fqdn(app.location, domainObjectMap[app.domain]);
+ app.alternateDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ app.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
}
function hasAccessTo(app, user, callback) {
@@ -492,7 +493,7 @@ function hasAccessTo(app, user, callback) {
function getDomainObjectMap(callback) {
assert.strictEqual(typeof callback, 'function');
- domaindb.getAll(function (error, domainObjects) {
+ domainsList(function (error, domainObjects) {
if (error) return callback(error);
let domainObjectMap = {};
@@ -713,7 +714,7 @@ function validateLocations(locations, callback) {
subdomain = subdomain.replace(/^\*\./, ''); // remove *.
}
- error = domains.validateHostname(subdomain, domainObjectMap[location.domain]);
+ error = dns.validateHostname(subdomain, domainObjectMap[location.domain]);
if (error) return callback(new BoxError(BoxError.BAD_FIELD, 'Bad location: ' + error.message, { field: 'location', domain: location.domain, subdomain: location.subdomain }));
}
@@ -841,9 +842,9 @@ function install(data, auditSource, callback) {
if (error) return callback(error);
const newApp = _.extend({}, _.omit(data, 'icon'), { appStoreId, manifest, location, domain, portBindings });
- newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]);
- newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
- newApp.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ newApp.fqdn = dns.fqdn(newApp.location, domainObjectMap[newApp.domain]);
+ newApp.alternateDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ newApp.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId, app: newApp, taskId: result.taskId });
@@ -1250,9 +1251,9 @@ function setLocation(app, data, auditSource, callback) {
if (error && error.reason === BoxError.ALREADY_EXISTS) error = getDuplicateErrorDetails(error.message, locations, domainObjectMap, data.portBindings);
if (error) return callback(error);
- values.fqdn = domains.fqdn(values.location, domainObjectMap[values.domain]);
- values.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
- values.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ values.fqdn = dns.fqdn(values.location, domainObjectMap[values.domain]);
+ values.alternateDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ values.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, _.extend({ appId, app, taskId: result.taskId }, values));
@@ -1723,9 +1724,9 @@ function clone(app, data, user, auditSource, callback) {
if (error) return callback(error);
const newApp = _.extend({}, _.omit(data, 'icon'), { appStoreId, manifest, location, domain, portBindings });
- newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]);
- newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
- newApp.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ newApp.fqdn = dns.fqdn(newApp.location, domainObjectMap[newApp.domain]);
+ newApp.alternateDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
+ newApp.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId: backupId, oldApp: app, newApp: newApp, taskId: result.taskId });
diff --git a/src/apptask.js b/src/apptask.js
index 47e7f6100..6d980cdae 100644
--- a/src/apptask.js
+++ b/src/apptask.js
@@ -22,6 +22,7 @@ const appdb = require('./appdb.js'),
constants = require('./constants.js'),
debug = require('debug')('box:apptask'),
df = require('@sindresorhus/df'),
+ dns = require('./dns.js'),
docker = require('./docker.js'),
domains = require('./domains.js'),
ejs = require('ejs'),
@@ -319,12 +320,12 @@ function waitForDnsPropagation(app, callback) {
sysinfo.getServerIp(function (error, ip) {
if (error) return callback(new BoxError(BoxError.NETWORK_ERROR, `Error getting public IP: ${error.message}`));
- domains.waitForDnsRecord(app.location, app.domain, 'A', ip, { times: 240 }, function (error) {
+ dns.waitForDnsRecord(app.location, app.domain, 'A', ip, { times: 240 }, function (error) {
if (error) return callback(new BoxError(BoxError.DNS_ERROR, `DNS Record is not synced yet: ${error.message}`, { ip: ip, subdomain: app.location, domain: app.domain }));
// now wait for alternateDomains and aliasDomains, if any
async.eachSeries(app.alternateDomains.concat(app.aliasDomains), function (domain, iteratorCallback) {
- domains.waitForDnsRecord(domain.subdomain, domain.domain, 'A', ip, { times: 240 }, function (error) {
+ dns.waitForDnsRecord(domain.subdomain, domain.domain, 'A', ip, { times: 240 }, function (error) {
if (error) return callback(new BoxError(BoxError.DNS_ERROR, `DNS Record is not synced yet: ${error.message}`, { ip: ip, subdomain: domain.subdomain, domain: domain.domain }));
iteratorCallback();
@@ -438,7 +439,7 @@ function install(app, args, progressCallback, callback) {
async.series([
progressCallback.bind(null, { percent: 30, message: 'Registering subdomains' }),
- domains.registerLocations.bind(null, [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback)
+ dns.registerLocations.bind(null, [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback)
], done);
},
@@ -589,7 +590,7 @@ function changeLocation(app, args, progressCallback, callback) {
if (obsoleteDomains.length === 0) return next();
- domains.unregisterLocations(obsoleteDomains, progressCallback, next);
+ dns.unregisterLocations(obsoleteDomains, progressCallback, next);
},
function setupDnsIfNeeded(done) {
@@ -597,7 +598,7 @@ function changeLocation(app, args, progressCallback, callback) {
async.series([
progressCallback.bind(null, { percent: 30, message: 'Registering subdomains' }),
- domains.registerLocations.bind(null, [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback)
+ dns.registerLocations.bind(null, [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains), { overwriteDns }, progressCallback)
], done);
},
@@ -931,7 +932,7 @@ function uninstall(app, args, progressCallback, callback) {
docker.deleteImage.bind(null, app.manifest),
progressCallback.bind(null, { percent: 70, message: 'Unregistering domains' }),
- domains.unregisterLocations.bind(null, [ { subdomain: app.location, domain: app.domain } ].concat(app.alternateDomains).concat(app.aliasDomains), progressCallback),
+ dns.unregisterLocations.bind(null, [ { subdomain: app.location, domain: app.domain } ].concat(app.alternateDomains).concat(app.aliasDomains), progressCallback),
progressCallback.bind(null, { percent: 90, message: 'Cleanup logs' }),
cleanupLogs.bind(null, app),
diff --git a/src/cloudron.js b/src/cloudron.js
index 20c187562..153bd5282 100644
--- a/src/cloudron.js
+++ b/src/cloudron.js
@@ -33,6 +33,7 @@ const apps = require('./apps.js'),
constants = require('./constants.js'),
cron = require('./cron.js'),
debug = require('debug')('box:cloudron'),
+ dns = require('./dns.js'),
domains = require('./domains.js'),
eventlog = require('./eventlog.js'),
fs = require('fs'),
@@ -55,6 +56,8 @@ const apps = require('./apps.js'),
const REBOOT_CMD = path.join(__dirname, 'scripts/reboot.sh');
+const domainsGet = util.callbackify(domains.get);
+
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
async function initialize() {
@@ -282,10 +285,10 @@ function prepareDashboardDomain(domain, auditSource, callback) {
if (settings.isDemo()) return callback(new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode'));
- domains.get(domain, function (error, domainObject) {
+ domainsGet(domain, function (error, domainObject) {
if (error) return callback(error);
- const fqdn = domains.fqdn(constants.DASHBOARD_LOCATION, domainObject);
+ const fqdn = dns.fqdn(constants.DASHBOARD_LOCATION, domainObject);
apps.getAll(async function (error, result) {
if (error) return callback(error);
@@ -311,13 +314,13 @@ function setDashboardDomain(domain, auditSource, callback) {
debug(`setDashboardDomain: ${domain}`);
- domains.get(domain, function (error, domainObject) {
+ domainsGet(domain, function (error, domainObject) {
if (error) return callback(error);
reverseProxy.writeDashboardConfig(domain, function (error) {
if (error) return callback(error);
- const fqdn = domains.fqdn(constants.DASHBOARD_LOCATION, domainObject);
+ const fqdn = dns.fqdn(constants.DASHBOARD_LOCATION, domainObject);
settings.setDashboardLocation(domain, fqdn, function (error) {
if (error) return callback(error);
@@ -367,21 +370,21 @@ function setupDnsAndCert(subdomain, domain, auditSource, progressCallback, callb
assert.strictEqual(typeof progressCallback, 'function');
assert.strictEqual(typeof callback, 'function');
- domains.get(domain, function (error, domainObject) {
+ domainsGet(domain, function (error, domainObject) {
if (error) return callback(error);
- const dashboardFqdn = domains.fqdn(subdomain, domainObject);
+ const dashboardFqdn = dns.fqdn(subdomain, domainObject);
sysinfo.getServerIp(function (error, ip) {
if (error) return callback(error);
async.series([
(done) => { progressCallback({ message: `Updating DNS of ${dashboardFqdn}` }); done(); },
- domains.upsertDnsRecords.bind(null, subdomain, domain, 'A', [ ip ]),
+ dns.upsertDnsRecords.bind(null, subdomain, domain, 'A', [ ip ]),
(done) => { progressCallback({ message: `Waiting for DNS of ${dashboardFqdn}` }); done(); },
- domains.waitForDnsRecord.bind(null, subdomain, domain, 'A', ip, { interval: 30000, times: 50000 }),
+ dns.waitForDnsRecord.bind(null, subdomain, domain, 'A', ip, { interval: 30000, times: 50000 }),
(done) => { progressCallback({ message: `Getting certificate of ${dashboardFqdn}` }); done(); },
- reverseProxy.ensureCertificate.bind(null, domains.fqdn(subdomain, domainObject), domain, auditSource)
+ reverseProxy.ensureCertificate.bind(null, dns.fqdn(subdomain, domainObject), domain, auditSource)
], function (error) {
if (error) return callback(error);
diff --git a/src/dns.js b/src/dns.js
new file mode 100644
index 000000000..630aed0f6
--- /dev/null
+++ b/src/dns.js
@@ -0,0 +1,375 @@
+'use strict';
+
+module.exports = exports = {
+ fqdn,
+ getName,
+
+ getDnsRecords,
+ upsertDnsRecords,
+ removeDnsRecords,
+
+ waitForDnsRecord,
+
+ validateHostname,
+
+ makeWildcard,
+
+ registerLocations,
+ unregisterLocations,
+
+ checkDnsRecords,
+ syncDnsRecords,
+
+ resolve
+};
+
+const apps = require('./apps.js'),
+ assert = require('assert'),
+ async = require('async'),
+ BoxError = require('./boxerror.js'),
+ constants = require('./constants.js'),
+ debug = require('debug')('box:domains'),
+ dns = require('dns'),
+ domains = require('./domains.js'),
+ mail = require('./mail.js'),
+ settings = require('./settings.js'),
+ sysinfo = require('./sysinfo.js'),
+ tld = require('tldjs'),
+ util = require('util'),
+ _ = require('underscore');
+
+const domainsGet = util.callbackify(domains.get),
+ domainsList = util.callbackify(domains.list);
+
+// choose which subdomain backend we use for test purpose we use route53
+function api(provider) {
+ assert.strictEqual(typeof provider, 'string');
+
+ switch (provider) {
+ case 'cloudflare': return require('./dns/cloudflare.js');
+ case 'route53': return require('./dns/route53.js');
+ case 'gcdns': return require('./dns/gcdns.js');
+ case 'digitalocean': return require('./dns/digitalocean.js');
+ case 'gandi': return require('./dns/gandi.js');
+ case 'godaddy': return require('./dns/godaddy.js');
+ case 'linode': return require('./dns/linode.js');
+ case 'vultr': return require('./dns/vultr.js');
+ case 'namecom': return require('./dns/namecom.js');
+ case 'namecheap': return require('./dns/namecheap.js');
+ case 'netcup': return require('./dns/netcup.js');
+ case 'noop': return require('./dns/noop.js');
+ case 'manual': return require('./dns/manual.js');
+ case 'wildcard': return require('./dns/wildcard.js');
+ default: return null;
+ }
+}
+
+function fqdn(location, domainObject) {
+ assert.strictEqual(typeof location, 'string');
+ assert.strictEqual(typeof domainObject, 'object');
+
+ return location + (location ? '.' : '') + domainObject.domain;
+}
+
+// Hostname validation comes from RFC 1123 (section 2.1)
+// Domain name validation comes from RFC 2181 (Name syntax)
+// https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
+// We are validating the validity of the location-fqdn as host name (and not dns name)
+function validateHostname(location, domainObject) {
+ assert.strictEqual(typeof location, 'string');
+ assert.strictEqual(typeof domainObject, 'object');
+
+ const hostname = fqdn(location, domainObject);
+
+ const RESERVED_LOCATIONS = [
+ constants.SMTP_LOCATION,
+ constants.IMAP_LOCATION
+ ];
+ if (RESERVED_LOCATIONS.indexOf(location) !== -1) return new BoxError(BoxError.BAD_FIELD, location + ' is reserved', { field: 'location' });
+
+ if (hostname === settings.dashboardFqdn()) return new BoxError(BoxError.BAD_FIELD, location + ' is reserved', { field: 'location' });
+
+ // workaround https://github.com/oncletom/tld.js/issues/73
+ var tmp = hostname.replace('_', '-');
+ if (!tld.isValid(tmp)) return new BoxError(BoxError.BAD_FIELD, 'Hostname is not a valid domain name', { field: 'location' });
+
+ if (hostname.length > 253) return new BoxError(BoxError.BAD_FIELD, 'Hostname length exceeds 253 characters', { field: 'location' });
+
+ if (location) {
+ // label validation
+ if (location.split('.').some(function (p) { return p.length > 63 || p.length < 1; })) return new BoxError(BoxError.BAD_FIELD, 'Invalid subdomain length', { field: 'location' });
+ if (location.match(/^[A-Za-z0-9-.]+$/) === null) return new BoxError(BoxError.BAD_FIELD, 'Subdomain can only contain alphanumeric, hyphen and dot', { field: 'location' });
+ if (/^[-.]/.test(location)) return new BoxError(BoxError.BAD_FIELD, 'Subdomain cannot start or end with hyphen or dot', { field: 'location' });
+ }
+
+ return null;
+}
+
+// returns the 'name' that needs to be inserted into zone
+// eslint-disable-next-line no-unused-vars
+function getName(domain, location, type) {
+ const part = domain.domain.slice(0, -domain.zoneName.length - 1);
+
+ if (location === '') return part;
+
+ return part ? `${location}.${part}` : location;
+}
+
+function getDnsRecords(location, domain, type, callback) {
+ assert.strictEqual(typeof location, 'string');
+ assert.strictEqual(typeof domain, 'string');
+ assert.strictEqual(typeof type, 'string');
+ assert.strictEqual(typeof callback, 'function');
+
+ domainsGet(domain, function (error, domainObject) {
+ if (error) return callback(error);
+
+ api(domainObject.provider).get(domainObject, location, type, function (error, values) {
+ if (error) return callback(error);
+
+ callback(null, values);
+ });
+ });
+}
+
+function checkDnsRecords(location, domain, callback) {
+ assert.strictEqual(typeof location, 'string');
+ assert.strictEqual(typeof domain, 'string');
+ assert.strictEqual(typeof callback, 'function');
+
+ getDnsRecords(location, domain, 'A', function (error, values) {
+ if (error) return callback(error);
+
+ sysinfo.getServerIp(function (error, ip) {
+ if (error) return callback(error);
+
+ if (values.length === 0) return callback(null, { needsOverwrite: false }); // does not exist
+ if (values[0] === ip) return callback(null, { needsOverwrite: false }); // exists but in sync
+
+ callback(null, { needsOverwrite: true });
+ });
+ });
+}
+
+// note: for TXT records the values must be quoted
+function upsertDnsRecords(location, domain, type, values, callback) {
+ assert.strictEqual(typeof location, 'string');
+ assert.strictEqual(typeof domain, 'string');
+ assert.strictEqual(typeof type, 'string');
+ assert(Array.isArray(values));
+ assert.strictEqual(typeof callback, 'function');
+
+ debug('upsertDNSRecord: %s on %s type %s values', location, domain, type, values);
+
+ domainsGet(domain, function (error, domainObject) {
+ if (error) return callback(error);
+
+ api(domainObject.provider).upsert(domainObject, location, type, values, function (error) {
+ if (error) return callback(error);
+
+ callback(null);
+ });
+ });
+}
+
+function removeDnsRecords(location, domain, type, values, callback) {
+ assert.strictEqual(typeof location, 'string');
+ assert.strictEqual(typeof domain, 'string');
+ assert.strictEqual(typeof type, 'string');
+ assert(Array.isArray(values));
+ assert.strictEqual(typeof callback, 'function');
+
+ debug('removeDNSRecord: %s on %s type %s values', location, domain, type, values);
+
+ domainsGet(domain, function (error, domainObject) {
+ if (error) return callback(error);
+
+ api(domainObject.provider).del(domainObject, location, type, values, function (error) {
+ if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
+
+ callback(null);
+ });
+ });
+}
+
+function waitForDnsRecord(location, domain, type, value, options, callback) {
+ assert.strictEqual(typeof location, 'string');
+ assert.strictEqual(typeof domain, 'string');
+ assert(type === 'A' || type === 'TXT');
+ assert.strictEqual(typeof value, 'string');
+ assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
+ assert.strictEqual(typeof callback, 'function');
+
+ domainsGet(domain, function (error, domainObject) {
+ if (error) return callback(error);
+
+ // linode DNS takes ~15mins
+ if (!options.interval) options.interval = domainObject.provider === 'linode' ? 20000 : 5000;
+
+ api(domainObject.provider).wait(domainObject, location, type, value, options, callback);
+ });
+}
+
+function makeWildcard(vhost) {
+ assert.strictEqual(typeof vhost, 'string');
+
+ // if the vhost is like *.example.com, this function will do nothing
+ let parts = vhost.split('.');
+ parts[0] = '*';
+ return parts.join('.');
+}
+
+function registerLocations(locations, options, progressCallback, callback) {
+ assert(Array.isArray(locations));
+ assert.strictEqual(typeof options, 'object');
+ assert.strictEqual(typeof progressCallback, 'function');
+ assert.strictEqual(typeof callback, 'function');
+
+ debug(`registerLocations: Will register ${JSON.stringify(locations)} with options ${JSON.stringify(options)}`);
+
+ const overwriteDns = options.overwriteDns || false;
+
+ sysinfo.getServerIp(function (error, ip) {
+ if (error) return callback(error);
+
+ async.eachSeries(locations, function (location, iteratorDone) {
+ async.retry({ times: 200, interval: 5000 }, function (retryCallback) {
+ progressCallback({ message: `Registering location: ${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}` });
+
+ // get the current record before updating it
+ getDnsRecords(location.subdomain, location.domain, 'A', function (error, values) {
+ if (error && error.reason === BoxError.EXTERNAL_ERROR) return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location })); // try again
+ if (error && error.reason === BoxError.ACCESS_DENIED) return retryCallback(null, new BoxError(BoxError.ACCESS_DENIED, error.message, { domain: location }));
+ if (error && error.reason === BoxError.NOT_FOUND) return retryCallback(null, new BoxError(BoxError.NOT_FOUND, error.message, { domain: location }));
+ if (error) return retryCallback(null, new BoxError(BoxError.EXTERNAL_ERROR, error.message, location)); // give up for other errors
+
+ if (values.length !== 0 && values[0] === ip) return retryCallback(null); // up-to-date
+
+ // refuse to update any existing DNS record for custom domains that we did not create
+ if (values.length !== 0 && !overwriteDns) return retryCallback(null, new BoxError(BoxError.ALREADY_EXISTS, 'DNS Record already exists', { domain: location }));
+
+ upsertDnsRecords(location.subdomain, location.domain, 'A', [ ip ], function (error) {
+ if (error && (error.reason === BoxError.BUSY || error.reason === BoxError.EXTERNAL_ERROR)) {
+ progressCallback({ message: `registerSubdomains: Upsert error. Will retry. ${error.message}` });
+ return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location })); // try again
+ }
+
+ retryCallback(null, error ? new BoxError(BoxError.EXTERNAL_ERROR, error.message, location) : null);
+ });
+ });
+ }, function (error, result) {
+ if (error || result) return iteratorDone(error || result);
+
+ iteratorDone(null);
+ });
+ }, callback);
+ });
+}
+
+function unregisterLocations(locations, progressCallback, callback) {
+ assert(Array.isArray(locations));
+ assert.strictEqual(typeof progressCallback, 'function');
+ assert.strictEqual(typeof callback, 'function');
+
+ sysinfo.getServerIp(function (error, ip) {
+ if (error) return callback(error);
+
+ async.eachSeries(locations, function (location, iteratorDone) {
+ async.retry({ times: 30, interval: 5000 }, function (retryCallback) {
+ progressCallback({ message: `Unregistering location: ${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}` });
+
+ removeDnsRecords(location.subdomain, location.domain, 'A', [ ip ], function (error) {
+ if (error && error.reason === BoxError.NOT_FOUND) return retryCallback(null, null);
+ if (error && (error.reason === BoxError.SBUSY || error.reason === BoxError.EXTERNAL_ERROR)) {
+ progressCallback({ message: `Error unregistering location. Will retry. ${error.message}`});
+ return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location })); // try again
+ }
+
+ retryCallback(null, error ? new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location }) : null);
+ });
+ }, function (error, result) {
+ if (error || result) return iteratorDone(error || result);
+
+ iteratorDone();
+ });
+ }, callback);
+ });
+}
+
+function syncDnsRecords(options, progressCallback, callback) {
+ assert.strictEqual(typeof options, 'object');
+ assert.strictEqual(typeof progressCallback, 'function');
+ assert.strictEqual(typeof callback, 'function');
+
+ if (options.domain && options.type === 'mail') return mail.setDnsRecords(options.domain, callback);
+
+ domainsList(function (error, domains) {
+ if (error) return callback(error);
+
+ if (options.domain) domains = domains.filter(d => d.domain === options.domain);
+
+ const mailSubdomain = settings.mailFqdn().substr(0, settings.mailFqdn().length - settings.mailDomain().length - 1);
+
+ apps.getAll(function (error, allApps) {
+ if (error) return callback(error);
+
+ let progress = 1, errors = [];
+
+ // we sync by domain only to get some nice progress
+ async.eachSeries(domains, function (domain, iteratorDone) {
+ progressCallback({ percent: progress, message: `Updating DNS of ${domain.domain}`});
+ progress += Math.round(100/(1+domains.length));
+
+ let locations = [];
+ if (domain.domain === settings.dashboardDomain()) locations.push({ subdomain: constants.DASHBOARD_LOCATION, domain: settings.dashboardDomain() });
+ if (domain.domain === settings.mailDomain() && settings.mailFqdn() !== settings.dashboardFqdn()) locations.push({ subdomain: mailSubdomain, domain: settings.mailDomain() });
+
+ allApps.forEach(function (app) {
+ const appLocations = [{ subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains);
+ locations = locations.concat(appLocations.filter(al => al.domain === domain.domain));
+ });
+
+ async.series([
+ registerLocations.bind(null, locations, { overwriteDns: true }, progressCallback),
+ progressCallback.bind(null, { message: `Updating mail DNS of ${domain.domain}`}),
+ mail.setDnsRecords.bind(null, domain.domain)
+ ], function (error) {
+ if (error) errors.push({ domain: domain.domain, message: error.message });
+ iteratorDone();
+ });
+ }, () => callback(null, { errors }));
+ });
+ });
+}
+
+// a note on TXT records. It doesn't have quotes ("") at the DNS level. Those quotes
+// are added for DNS server software to enclose spaces. Such quotes may also be returned
+// by the DNS REST API of some providers
+function resolve(hostname, rrtype, options, callback) {
+ assert.strictEqual(typeof hostname, 'string');
+ assert.strictEqual(typeof rrtype, 'string');
+ assert(options && typeof options === 'object');
+ assert.strictEqual(typeof callback, 'function');
+
+ const defaultOptions = { server: '127.0.0.1', timeout: 5000 }; // unbound runs on 127.0.0.1
+ const resolver = new dns.Resolver();
+ options = _.extend({ }, defaultOptions, options);
+
+ // Only use unbound on a Cloudron
+ if (constants.CLOUDRON) resolver.setServers([ options.server ]);
+
+ // should callback with ECANCELLED but looks like we might hit https://github.com/nodejs/node/issues/14814
+ const timerId = setTimeout(resolver.cancel.bind(resolver), options.timeout || 5000);
+
+ resolver.resolve(hostname, rrtype, function (error, result) {
+ clearTimeout(timerId);
+
+ if (error && error.code === 'ECANCELLED') error.code = 'TIMEOUT';
+
+ // result is an empty array if there was no error but there is no record. when you query a random
+ // domain, it errors with ENOTFOUND. But if you query an existing domain (A record) but with different
+ // type (CNAME) it is not an error and empty array
+ // for TXT records, result is 2d array of strings
+ callback(error, result);
+ });
+}
diff --git a/src/dns/cloudflare.js b/src/dns/cloudflare.js
index 36dabfb30..90c52fda1 100644
--- a/src/dns/cloudflare.js
+++ b/src/dns/cloudflare.js
@@ -1,22 +1,21 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
async = require('async'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/cloudflare'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
superagent = require('superagent'),
util = require('util'),
waitForDns = require('./waitfordns.js'),
@@ -115,7 +114,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
debug('upsert: %s for zone %s of type %s with values %j', fqdn, zoneName, type, values);
@@ -186,7 +185,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
getZoneByName(dnsConfig, zoneName, function(error, zone) {
if (error) return callback(error);
@@ -211,7 +210,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
getZoneByName(dnsConfig, zoneName, function(error, zone) {
if (error) return callback(error);
@@ -256,7 +255,7 @@ function wait(domainObject, location, type, value, options, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
debug('wait: %s for zone %s of type %s', fqdn, zoneName, type);
diff --git a/src/dns/digitalocean.js b/src/dns/digitalocean.js
index 5829574f2..e6c6799f2 100644
--- a/src/dns/digitalocean.js
+++ b/src/dns/digitalocean.js
@@ -15,8 +15,7 @@ const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/digitalocean'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
safe = require('safetydance'),
superagent = require('superagent'),
util = require('util'),
@@ -87,7 +86,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug('upsert: %s for zone %s of type %s with values %j', name, zoneName, type, values);
@@ -167,7 +166,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
getInternal(dnsConfig, zoneName, name, type, function (error, result) {
if (error) return callback(error);
@@ -190,7 +189,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
getInternal(dnsConfig, zoneName, name, type, function (error, result) {
if (error) return callback(error);
@@ -230,7 +229,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/gandi.js b/src/dns/gandi.js
index ea6281dac..2912e2cf6 100644
--- a/src/dns/gandi.js
+++ b/src/dns/gandi.js
@@ -1,26 +1,25 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/gandi'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
superagent = require('superagent'),
util = require('util'),
waitForDns = require('./waitfordns.js');
-var GANDI_API = 'https://dns.api.gandi.net/api/v5';
+const GANDI_API = 'https://dns.api.gandi.net/api/v5';
function formatError(response) {
return util.format(`Gandi DNS error [${response.statusCode}] ${response.body.message}`);
@@ -44,7 +43,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug(`upsert: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
@@ -75,7 +74,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug(`get: ${name} in zone ${zoneName} of type ${type}`);
@@ -103,7 +102,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug(`del: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
@@ -130,7 +129,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/gcdns.js b/src/dns/gcdns.js
index 109962fe6..2304c7187 100644
--- a/src/dns/gcdns.js
+++ b/src/dns/gcdns.js
@@ -1,23 +1,21 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/gcdns'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
GCDNS = require('@google-cloud/dns').DNS,
- util = require('util'),
waitForDns = require('./waitfordns.js'),
_ = require('underscore');
@@ -78,7 +76,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
debug('add: %s for zone %s of type %s with values %j', fqdn, zoneName, type, values);
@@ -120,7 +118,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
getZoneByName(getDnsCredentials(dnsConfig), zoneName, function (error, zone) {
if (error) return callback(error);
@@ -149,7 +147,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
getZoneByName(getDnsCredentials(dnsConfig), zoneName, function (error, zone) {
if (error) return callback(error);
@@ -183,7 +181,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/godaddy.js b/src/dns/godaddy.js
index 3e339d344..fe5be0855 100644
--- a/src/dns/godaddy.js
+++ b/src/dns/godaddy.js
@@ -1,21 +1,20 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/godaddy'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
superagent = require('superagent'),
util = require('util'),
waitForDns = require('./waitfordns.js');
@@ -50,7 +49,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug(`upsert: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
@@ -91,7 +90,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug(`get: ${name} in zone ${zoneName} of type ${type}`);
@@ -123,7 +122,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug(`get: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
@@ -165,7 +164,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/interface.js b/src/dns/interface.js
index 963fc523d..a6e7bf820 100644
--- a/src/dns/interface.js
+++ b/src/dns/interface.js
@@ -7,18 +7,17 @@
// -------------------------------------------
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
- BoxError = require('../boxerror.js'),
- util = require('util');
+const assert = require('assert'),
+ BoxError = require('../boxerror.js');
function removePrivateFields(domainObject) {
// in-place removal of tokens and api keys with constants.SECRET_PLACEHOLDER
diff --git a/src/dns/linode.js b/src/dns/linode.js
index f0299f153..65c0ecc5d 100644
--- a/src/dns/linode.js
+++ b/src/dns/linode.js
@@ -10,13 +10,12 @@ exports = module.exports = {
verifyDnsConfig
};
-let async = require('async'),
+const async = require('async'),
assert = require('assert'),
constants = require('../constants.js'),
BoxError = require('../boxerror.js'),
debug = require('debug')('box:dns/linode'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
superagent = require('superagent'),
util = require('util'),
waitForDns = require('./waitfordns.js');
@@ -117,7 +116,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
getZoneRecords(dnsConfig, zoneName, name, type, function (error, result) {
if (error) return callback(error);
@@ -140,7 +139,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
debug('upsert: %s for zone %s of type %s with values %j', name, zoneName, type, values);
@@ -222,7 +221,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
getZoneRecords(dnsConfig, zoneName, name, type, function (error, result) {
if (error) return callback(error);
@@ -263,7 +262,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/manual.js b/src/dns/manual.js
index 0398521c6..9e605cb73 100644
--- a/src/dns/manual.js
+++ b/src/dns/manual.js
@@ -10,12 +10,10 @@ exports = module.exports = {
verifyDnsConfig: verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
debug = require('debug')('box:dns/manual'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
- util = require('util'),
+ dns = require('../dns.js'),
waitForDns = require('./waitfordns.js');
function removePrivateFields(domainObject) {
@@ -66,7 +64,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/namecheap.js b/src/dns/namecheap.js
index 1e79c6c5d..1df9e60c1 100644
--- a/src/dns/namecheap.js
+++ b/src/dns/namecheap.js
@@ -1,21 +1,20 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- verifyDnsConfig: verifyDnsConfig,
- wait: wait
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ verifyDnsConfig,
+ wait
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/namecheap'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
querystring = require('querystring'),
safe = require('safetydance'),
superagent = require('superagent'),
@@ -151,7 +150,7 @@ function upsert(domainObject, subdomain, type, values, callback) {
const dnsConfig = domainObject.config;
const zoneName = domainObject.zoneName;
- subdomain = domains.getName(domainObject, subdomain, type) || '@';
+ subdomain = dns.getName(domainObject, subdomain, type) || '@';
debug('upsert: %s for zone %s of type %s with values %j', subdomain, zoneName, type, values);
@@ -214,7 +213,7 @@ function get(domainObject, subdomain, type, callback) {
const dnsConfig = domainObject.config;
const zoneName = domainObject.zoneName;
- subdomain = domains.getName(domainObject, subdomain, type) || '@';
+ subdomain = dns.getName(domainObject, subdomain, type) || '@';
getZone(dnsConfig, zoneName, function (error, result) {
if (error) return callback(error);
@@ -241,7 +240,7 @@ function del(domainObject, subdomain, type, values, callback) {
const dnsConfig = domainObject.config;
const zoneName = domainObject.zoneName;
- subdomain = domains.getName(domainObject, subdomain, type) || '@';
+ subdomain = dns.getName(domainObject, subdomain, type) || '@';
debug('del: %s for zone %s of type %s with values %j', subdomain, zoneName, type, values);
@@ -316,7 +315,7 @@ function wait(domainObject, subdomain, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(subdomain, domainObject);
+ const fqdn = dns.fqdn(subdomain, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/namecom.js b/src/dns/namecom.js
index 6a1298e5a..1049f6cd7 100644
--- a/src/dns/namecom.js
+++ b/src/dns/namecom.js
@@ -1,24 +1,22 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/namecom'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
safe = require('safetydance'),
superagent = require('superagent'),
- util = require('util'),
waitForDns = require('./waitfordns.js');
const NAMECOM_API = 'https://api.name.com/v4';
@@ -162,7 +160,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
debug(`upsert: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
@@ -183,7 +181,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
getInternal(dnsConfig, zoneName, name, type, function (error, result) {
if (error) return callback(error);
@@ -205,7 +203,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
debug(`del: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`);
@@ -235,7 +233,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/netcup.js b/src/dns/netcup.js
index adc59b186..3cebcdf76 100644
--- a/src/dns/netcup.js
+++ b/src/dns/netcup.js
@@ -1,26 +1,25 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/netcup'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
superagent = require('superagent'),
util = require('util'),
waitForDns = require('./waitfordns.js');
-var API_ENDPOINT = 'https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON';
+const API_ENDPOINT = 'https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON';
function formatError(response) {
if (response.body) return util.format('Netcup DNS error [%s] %s', response.body.statuscode, response.body.longmessage);
@@ -95,7 +94,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug('upsert: %s for zone %s of type %s with values %j', name, zoneName, type, values);
@@ -163,7 +162,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug('get: %s for zone %s of type %s', name, zoneName, type);
@@ -188,7 +187,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '@';
+ name = dns.getName(domainObject, location, type) || '@';
debug('del: %s for zone %s of type %s with values %j', name, zoneName, type, values);
@@ -249,7 +248,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/noop.js b/src/dns/noop.js
index ed6efe119..3c0124baf 100644
--- a/src/dns/noop.js
+++ b/src/dns/noop.js
@@ -1,18 +1,17 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
- debug = require('debug')('box:dns/noop'),
- util = require('util');
+const assert = require('assert'),
+ debug = require('debug')('box:dns/noop');
function removePrivateFields(domainObject) {
return domainObject;
diff --git a/src/dns/route53.js b/src/dns/route53.js
index 413edd58e..4fe6fac74 100644
--- a/src/dns/route53.js
+++ b/src/dns/route53.js
@@ -1,23 +1,21 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
AWS = require('aws-sdk'),
BoxError = require('../boxerror.js'),
constants = require('../constants.js'),
debug = require('debug')('box:dns/route53'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
- util = require('util'),
+ dns = require('../dns.js'),
waitForDns = require('./waitfordns.js'),
_ = require('underscore');
@@ -102,7 +100,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
debug('add: %s for zone %s of type %s with values %j', fqdn, zoneName, type, values);
@@ -147,7 +145,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
getZoneByName(dnsConfig, zoneName, function (error, zone) {
if (error) return callback(error);
@@ -183,7 +181,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- fqdn = domains.fqdn(location, domainObject);
+ fqdn = dns.fqdn(location, domainObject);
getZoneByName(dnsConfig, zoneName, function (error, zone) {
if (error) return callback(error);
@@ -241,7 +239,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/vultr.js b/src/dns/vultr.js
index 29d7a1584..f7e0187c6 100644
--- a/src/dns/vultr.js
+++ b/src/dns/vultr.js
@@ -15,8 +15,7 @@ const async = require('async'),
constants = require('../constants.js'),
BoxError = require('../boxerror.js'),
debug = require('debug')('box:dns/vultr'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
safe = require('safetydance'),
superagent = require('superagent'),
util = require('util'),
@@ -87,7 +86,7 @@ function get(domainObject, location, type, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
getZoneRecords(dnsConfig, zoneName, name, type, function (error, records) {
if (error) return callback(error);
@@ -109,7 +108,7 @@ function upsert(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
debug('upsert: %s for zone %s of type %s with values %j', name, zoneName, type, values);
@@ -190,7 +189,7 @@ function del(domainObject, location, type, values, callback) {
const dnsConfig = domainObject.config,
zoneName = domainObject.zoneName,
- name = domains.getName(domainObject, location, type) || '';
+ name = dns.getName(domainObject, location, type) || '';
getZoneRecords(dnsConfig, zoneName, name, type, function (error, records) {
if (error) return callback(error);
@@ -230,7 +229,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
diff --git a/src/dns/waitfordns.js b/src/dns/waitfordns.js
index 916a12ba0..a93ca3324 100644
--- a/src/dns/waitfordns.js
+++ b/src/dns/waitfordns.js
@@ -2,11 +2,11 @@
exports = module.exports = waitForDns;
-var assert = require('assert'),
+const assert = require('assert'),
async = require('async'),
BoxError = require('../boxerror.js'),
debug = require('debug')('box:dns/waitfordns'),
- dns = require('../native-dns.js');
+ dns = require('../dns.js');
function resolveIp(hostname, options, callback) {
assert.strictEqual(typeof hostname, 'string');
diff --git a/src/dns/wildcard.js b/src/dns/wildcard.js
index 7930c676e..949e98109 100644
--- a/src/dns/wildcard.js
+++ b/src/dns/wildcard.js
@@ -1,22 +1,20 @@
'use strict';
exports = module.exports = {
- removePrivateFields: removePrivateFields,
- injectPrivateFields: injectPrivateFields,
- upsert: upsert,
- get: get,
- del: del,
- wait: wait,
- verifyDnsConfig: verifyDnsConfig
+ removePrivateFields,
+ injectPrivateFields,
+ upsert,
+ get,
+ del,
+ wait,
+ verifyDnsConfig
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('../boxerror.js'),
debug = require('debug')('box:dns/manual'),
- dns = require('../native-dns.js'),
- domains = require('../domains.js'),
+ dns = require('../dns.js'),
sysinfo = require('../sysinfo.js'),
- util = require('util'),
waitForDns = require('./waitfordns.js');
function removePrivateFields(domainObject) {
@@ -66,7 +64,7 @@ function wait(domainObject, location, type, value, options, callback) {
assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
waitForDns(fqdn, domainObject.zoneName, type, value, options, callback);
}
@@ -83,7 +81,7 @@ function verifyDnsConfig(domainObject, callback) {
if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' }));
const location = 'cloudrontestdns';
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
dns.resolve(fqdn, 'A', { server: '127.0.0.1', timeout: 5000 }, function (error, result) {
if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, `Unable to resolve ${fqdn}`, { field: 'nameservers' }));
diff --git a/src/domaindb.js b/src/domaindb.js
deleted file mode 100644
index c003d1f7c..000000000
--- a/src/domaindb.js
+++ /dev/null
@@ -1,141 +0,0 @@
-/* jslint node:true */
-
-'use strict';
-
-exports = module.exports = {
- add,
- get,
- getAll,
- update,
- del,
- clear
-};
-
-const assert = require('assert'),
- BoxError = require('./boxerror.js'),
- database = require('./database.js'),
- safe = require('safetydance');
-
-const DOMAINS_FIELDS = [ 'domain', 'zoneName', 'provider', 'configJson', 'tlsConfigJson', 'wellKnownJson', 'fallbackCertificateJson' ].join(',');
-
-function postProcess(data) {
- data.config = safe.JSON.parse(data.configJson);
- delete data.configJson;
-
- data.tlsConfig = safe.JSON.parse(data.tlsConfigJson);
- delete data.tlsConfigJson;
-
- data.wellKnown = safe.JSON.parse(data.wellKnownJson);
- delete data.wellKnownJson;
-
- data.fallbackCertificate = safe.JSON.parse(data.fallbackCertificateJson);
- delete data.fallbackCertificateJson;
-
- return data;
-}
-
-function get(domain, callback) {
- assert.strictEqual(typeof domain, 'string');
- assert.strictEqual(typeof callback, 'function');
-
- database.query(`SELECT ${DOMAINS_FIELDS} FROM domains WHERE domain=?`, [ domain ], function (error, result) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
- if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Domain not found'));
-
- postProcess(result[0]);
-
- callback(null, result[0]);
- });
-}
-
-function getAll(callback) {
- database.query(`SELECT ${DOMAINS_FIELDS} FROM domains ORDER BY domain`, function (error, results) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
-
- results.forEach(postProcess);
-
- callback(null, results);
- });
-}
-
-function add(name, data, callback) {
- assert.strictEqual(typeof name, 'string');
- assert.strictEqual(typeof data, 'object');
- assert.strictEqual(typeof data.zoneName, 'string');
- assert.strictEqual(typeof data.provider, 'string');
- assert.strictEqual(typeof data.config, 'object');
- assert.strictEqual(typeof data.tlsConfig, 'object');
- assert.strictEqual(typeof data.fallbackCertificate, 'object');
- assert.strictEqual(typeof callback, 'function');
-
- let queries = [
- { query: 'INSERT INTO domains (domain, zoneName, provider, configJson, tlsConfigJson, fallbackCertificateJson) VALUES (?, ?, ?, ?, ?, ?)',
- args: [ name, data.zoneName, data.provider, JSON.stringify(data.config), JSON.stringify(data.tlsConfig), JSON.stringify(data.fallbackCertificate) ] },
- { query: 'INSERT INTO mail (domain, dkimSelector) VALUES (?, ?)', args: [ name, data.dkimSelector || 'cloudron' ] },
- ];
-
- database.transaction(queries, function (error) {
- if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, 'Domain already exists'));
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
-
- callback(null);
- });
-}
-
-function update(name, domain, callback) {
- assert.strictEqual(typeof name, 'string');
- assert.strictEqual(typeof domain, 'object');
- assert.strictEqual(typeof callback, 'function');
-
- var args = [ ], fields = [ ];
- for (var k in domain) {
- if (k === 'config' || k === 'tlsConfig' || k === 'wellKnown' || k === 'fallbackCertificate') { // json fields
- fields.push(`${k}Json = ?`);
- args.push(JSON.stringify(domain[k]));
- } else {
- fields.push(k + ' = ?');
- args.push(domain[k]);
- }
- }
- args.push(name);
-
- database.query('UPDATE domains SET ' + fields.join(', ') + ' WHERE domain=?', args, function (error) {
- if (error && error.reason === BoxError.NOT_FOUND) return callback(new BoxError(BoxError.NOT_FOUND, 'Domain not found'));
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
-
- callback(null);
- });
-}
-
-function del(domain, callback) {
- assert.strictEqual(typeof domain, 'string');
- assert.strictEqual(typeof callback, 'function');
-
- let queries = [
- { query: 'DELETE FROM mail WHERE domain = ?', args: [ domain ] },
- { query: 'DELETE FROM domains WHERE domain = ?', args: [ domain ] },
- ];
-
- database.transaction(queries, function (error, results) {
- if (error && error.code === 'ER_ROW_IS_REFERENCED_2') {
- if (error.message.indexOf('apps_mailDomain_constraint') !== -1) return callback(new BoxError(BoxError.CONFLICT, 'Domain is in use by an app or the mailbox of an app. Check the domains of apps and the Email section of each app.'));
- if (error.message.indexOf('subdomains') !== -1) return callback(new BoxError(BoxError.CONFLICT, 'Domain is in use by one or more app(s).'));
- if (error.message.indexOf('mail') !== -1) return callback(new BoxError(BoxError.CONFLICT, 'Domain is in use by one or more mailboxes. Delete them first in the Email view.'));
-
- return callback(new BoxError(BoxError.CONFLICT, error.message));
- }
-
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
- if (results[1].affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Domain not found'));
-
- callback(null);
- });
-}
-
-function clear(callback) {
- database.query('DELETE FROM domains', function (error) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
-
- callback(error);
- });
-}
diff --git a/src/domains.js b/src/domains.js
index ff15a0b69..db45007b8 100644
--- a/src/domains.js
+++ b/src/domains.js
@@ -3,54 +3,49 @@
module.exports = exports = {
add,
get,
- getAll,
+ list,
update,
del,
clear,
- fqdn,
- getName,
-
- getDnsRecords,
- upsertDnsRecords,
- removeDnsRecords,
-
- waitForDnsRecord,
-
removePrivateFields,
removeRestrictedFields,
-
- validateHostname,
-
- makeWildcard,
-
- parentDomain,
-
- registerLocations,
- unregisterLocations,
-
- checkDnsRecords,
- syncDnsRecords
};
-const apps = require('./apps.js'),
- assert = require('assert'),
- async = require('async'),
+const assert = require('assert'),
BoxError = require('./boxerror.js'),
- constants = require('./constants.js'),
crypto = require('crypto'),
+ database = require('./database.js'),
debug = require('debug')('box:domains'),
- domaindb = require('./domaindb.js'),
eventlog = require('./eventlog.js'),
mail = require('./mail.js'),
reverseProxy = require('./reverseproxy.js'),
+ safe = require('safetydance'),
settings = require('./settings.js'),
- sysinfo = require('./sysinfo.js'),
tld = require('tldjs'),
+ util = require('util'),
_ = require('underscore');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
+const DOMAINS_FIELDS = [ 'domain', 'zoneName', 'provider', 'configJson', 'tlsConfigJson', 'wellKnownJson', 'fallbackCertificateJson' ].join(',');
+
+function postProcess(data) {
+ data.config = safe.JSON.parse(data.configJson);
+ delete data.configJson;
+
+ data.tlsConfig = safe.JSON.parse(data.tlsConfigJson);
+ delete data.tlsConfigJson;
+
+ data.wellKnown = safe.JSON.parse(data.wellKnownJson);
+ delete data.wellKnownJson;
+
+ data.fallbackCertificate = safe.JSON.parse(data.fallbackCertificateJson);
+ delete data.fallbackCertificateJson;
+
+ return data;
+}
+
// choose which subdomain backend we use for test purpose we use route53
function api(provider) {
assert.strictEqual(typeof provider, 'string');
@@ -74,11 +69,6 @@ function api(provider) {
}
}
-function parentDomain(domain) {
- assert.strictEqual(typeof domain, 'string');
- return domain.replace(/^\S+?\./, ''); // +? means non-greedy
-}
-
function verifyDnsConfig(dnsConfig, domain, zoneName, provider, callback) {
assert(dnsConfig && typeof dnsConfig === 'object'); // the dns config to test with
assert.strictEqual(typeof domain, 'string');
@@ -100,44 +90,6 @@ function verifyDnsConfig(dnsConfig, domain, zoneName, provider, callback) {
});
}
-function fqdn(location, domainObject) {
- return location + (location ? '.' : '') + domainObject.domain;
-}
-
-// Hostname validation comes from RFC 1123 (section 2.1)
-// Domain name validation comes from RFC 2181 (Name syntax)
-// https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
-// We are validating the validity of the location-fqdn as host name (and not dns name)
-function validateHostname(location, domainObject) {
- assert.strictEqual(typeof location, 'string');
- assert.strictEqual(typeof domainObject, 'object');
-
- const hostname = fqdn(location, domainObject);
-
- const RESERVED_LOCATIONS = [
- constants.SMTP_LOCATION,
- constants.IMAP_LOCATION
- ];
- if (RESERVED_LOCATIONS.indexOf(location) !== -1) return new BoxError(BoxError.BAD_FIELD, location + ' is reserved', { field: 'location' });
-
- if (hostname === settings.dashboardFqdn()) return new BoxError(BoxError.BAD_FIELD, location + ' is reserved', { field: 'location' });
-
- // workaround https://github.com/oncletom/tld.js/issues/73
- var tmp = hostname.replace('_', '-');
- if (!tld.isValid(tmp)) return new BoxError(BoxError.BAD_FIELD, 'Hostname is not a valid domain name', { field: 'location' });
-
- if (hostname.length > 253) return new BoxError(BoxError.BAD_FIELD, 'Hostname length exceeds 253 characters', { field: 'location' });
-
- if (location) {
- // label validation
- if (location.split('.').some(function (p) { return p.length > 63 || p.length < 1; })) return new BoxError(BoxError.BAD_FIELD, 'Invalid subdomain length', { field: 'location' });
- if (location.match(/^[A-Za-z0-9-.]+$/) === null) return new BoxError(BoxError.BAD_FIELD, 'Subdomain can only contain alphanumeric, hyphen and dot', { field: 'location' });
- if (/^[-.]/.test(location)) return new BoxError(BoxError.BAD_FIELD, 'Subdomain cannot start or end with hyphen or dot', { field: 'location' });
- }
-
- return null;
-}
-
function validateTlsConfig(tlsConfig, dnsProvider) {
assert.strictEqual(typeof tlsConfig, 'object');
assert.strictEqual(typeof dnsProvider, 'string');
@@ -165,37 +117,36 @@ function validateWellKnown(wellKnown) {
return null;
}
-function add(domain, data, auditSource, callback) {
+async function add(domain, data, auditSource) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof data.zoneName, 'string');
assert.strictEqual(typeof data.provider, 'string');
assert.strictEqual(typeof data.config, 'object');
assert.strictEqual(typeof data.fallbackCertificate, 'object');
assert.strictEqual(typeof data.tlsConfig, 'object');
- assert.strictEqual(typeof callback, 'function');
let { zoneName, provider, config, fallbackCertificate, tlsConfig, dkimSelector } = data;
- if (!tld.isValid(domain)) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid domain', { field: 'domain' }));
- if (domain.endsWith('.')) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid domain', { field: 'domain' }));
+ if (!tld.isValid(domain)) throw new BoxError(BoxError.BAD_FIELD, 'Invalid domain', { field: 'domain' });
+ if (domain.endsWith('.')) throw new BoxError(BoxError.BAD_FIELD, 'Invalid domain', { field: 'domain' });
if (zoneName) {
- if (!tld.isValid(zoneName)) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid zoneName', { field: 'zoneName' }));
- if (zoneName.endsWith('.')) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid zoneName', { field: 'zoneName' }));
+ if (!tld.isValid(zoneName)) throw new BoxError(BoxError.BAD_FIELD, 'Invalid zoneName', { field: 'zoneName' });
+ if (zoneName.endsWith('.')) throw new BoxError(BoxError.BAD_FIELD, 'Invalid zoneName', { field: 'zoneName' });
} else {
zoneName = tld.getDomain(domain) || domain;
}
if (fallbackCertificate) {
let error = reverseProxy.validateCertificate('test', { domain, config }, fallbackCertificate);
- if (error) return callback(error);
+ if (error) throw error;
} else {
fallbackCertificate = reverseProxy.generateFallbackCertificateSync(domain);
- if (fallbackCertificate.error) return callback(fallbackCertificate.error);
+ if (fallbackCertificate.error) throw fallbackCertificate.error;
}
let error = validateTlsConfig(tlsConfig, provider);
- if (error) return callback(error);
+ if (error) throw error;
if (!dkimSelector) {
// create a unique suffix. this lets one add this domain can be added in another cloudron instance and not have their dkim selector conflict
@@ -203,47 +154,42 @@ function add(domain, data, auditSource, callback) {
dkimSelector = `cloudron-${suffix}`;
}
- verifyDnsConfig(config, domain, zoneName, provider, function (error, sanitizedConfig) {
- if (error) return callback(error);
+ const verifyDnsConfigAsync = util.promisify(verifyDnsConfig);
- domaindb.add(domain, { zoneName, provider, config: sanitizedConfig, tlsConfig, dkimSelector, fallbackCertificate }, function (error) {
- if (error) return callback(error);
+ const sanitizedConfig = await verifyDnsConfigAsync(config, domain, zoneName, provider);
- reverseProxy.setFallbackCertificate(domain, fallbackCertificate, function (error) {
- if (error) return callback(error);
+ let queries = [
+ { query: 'INSERT INTO domains (domain, zoneName, provider, configJson, tlsConfigJson, fallbackCertificateJson) VALUES (?, ?, ?, ?, ?, ?)',
+ args: [ domain, zoneName, provider, JSON.stringify(sanitizedConfig), JSON.stringify(tlsConfig), JSON.stringify(fallbackCertificate) ] },
+ { query: 'INSERT INTO mail (domain, dkimSelector) VALUES (?, ?)', args: [ domain, dkimSelector || 'cloudron' ] },
+ ];
- eventlog.add(eventlog.ACTION_DOMAIN_ADD, auditSource, { domain, zoneName, provider });
+ [error] = await safe(database.transaction(queries));
+ if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'Domain already exists');
+ if (error) throw new BoxError(BoxError.DATABASE_ERROR, error);
- mail.onDomainAdded(domain, NOOP_CALLBACK);
+ await reverseProxy.setFallbackCertificate(domain, fallbackCertificate);
- callback();
- });
- });
- });
+ eventlog.add(eventlog.ACTION_DOMAIN_ADD, auditSource, { domain, zoneName, provider });
+
+ mail.onDomainAdded(domain, NOOP_CALLBACK);
}
-function get(domain, callback) {
+async function get(domain) {
assert.strictEqual(typeof domain, 'string');
- assert.strictEqual(typeof callback, 'function');
- domaindb.get(domain, function (error, result) {
- if (error) return callback(error);
-
- return callback(null, result);
- });
+ const result = await database.query(`SELECT ${DOMAINS_FIELDS} FROM domains WHERE domain=?`, [ domain ]);
+ if (result.length === 0) return null;
+ return postProcess(result[0]);
}
-function getAll(callback) {
- assert.strictEqual(typeof callback, 'function');
-
- domaindb.getAll(function (error, result) {
- if (error) return callback(error);
-
- return callback(null, result);
- });
+async function list() {
+ const results = await database.query(`SELECT ${DOMAINS_FIELDS} FROM domains ORDER BY domain`);
+ results.forEach(postProcess);
+ return results;
}
-function update(domain, data, auditSource, callback) {
+async function update(domain, data, auditSource) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof data.zoneName, 'string');
assert.strictEqual(typeof data.provider, 'string');
@@ -251,196 +197,98 @@ function update(domain, data, auditSource, callback) {
assert.strictEqual(typeof data.fallbackCertificate, 'object');
assert.strictEqual(typeof data.tlsConfig, 'object');
assert.strictEqual(typeof auditSource, 'object');
- assert.strictEqual(typeof callback, 'function');
let { zoneName, provider, config, fallbackCertificate, tlsConfig, wellKnown } = data;
+ let error;
- if (settings.isDemo() && (domain === settings.dashboardDomain())) return callback(new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode'));
+ if (settings.isDemo() && (domain === settings.dashboardDomain())) throw new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode');
- domaindb.get(domain, function (error, domainObject) {
- if (error) return callback(error);
+ const domainObject = await get(domain);
+ if (zoneName) {
+ if (!tld.isValid(zoneName)) throw new BoxError(BoxError.BAD_FIELD, 'Invalid zoneName', { field: 'zoneName' });
+ } else {
+ zoneName = domainObject.zoneName;
+ }
- if (zoneName) {
- if (!tld.isValid(zoneName)) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid zoneName', { field: 'zoneName' }));
+ if (fallbackCertificate) {
+ let error = reverseProxy.validateCertificate('test', domainObject, fallbackCertificate);
+ if (error) throw error;
+ }
+
+ error = validateTlsConfig(tlsConfig, provider);
+ if (error) throw error;
+
+ error = validateWellKnown(wellKnown, provider);
+ if (error) throw error;
+
+ if (provider === domainObject.provider) api(provider).injectPrivateFields(config, domainObject.config);
+
+ const verifyDnsConfigAsync = util.promisify(verifyDnsConfig);
+
+ const sanitizedConfig = await verifyDnsConfigAsync(config, domain, zoneName, provider);
+
+ let newData = {
+ config: sanitizedConfig,
+ zoneName,
+ provider,
+ tlsConfig,
+ wellKnown,
+ };
+
+ if (fallbackCertificate) newData.fallbackCertificate = fallbackCertificate;
+
+ let args = [ ], fields = [ ];
+ for (const k in newData) {
+ if (k === 'config' || k === 'tlsConfig' || k === 'wellKnown' || k === 'fallbackCertificate') { // json fields
+ fields.push(`${k}Json = ?`);
+ args.push(JSON.stringify(newData[k]));
} else {
- zoneName = domainObject.zoneName;
+ fields.push(k + ' = ?');
+ args.push(newData[k]);
}
+ }
+ args.push(domain);
- if (fallbackCertificate) {
- let error = reverseProxy.validateCertificate('test', domainObject, fallbackCertificate);
- if (error) return callback(error);
- }
+ [error] = await safe(database.query('UPDATE domains SET ' + fields.join(', ') + ' WHERE domain=?', args));
+ if (error && error.reason === BoxError.NOT_FOUND) throw new BoxError(BoxError.NOT_FOUND, 'Domain not found');
+ if (error) throw new BoxError(BoxError.DATABASE_ERROR, error);
- error = validateTlsConfig(tlsConfig, provider);
- if (error) return callback(error);
+ if (!fallbackCertificate) return;
- error = validateWellKnown(wellKnown, provider);
- if (error) return callback(error);
+ await reverseProxy.setFallbackCertificate(domain, fallbackCertificate);
- if (provider === domainObject.provider) api(provider).injectPrivateFields(config, domainObject.config);
-
- verifyDnsConfig(config, domain, zoneName, provider, function (error, sanitizedConfig) {
- if (error) return callback(error);
-
- let newData = {
- config: sanitizedConfig,
- zoneName,
- provider,
- tlsConfig,
- wellKnown,
- };
-
- if (fallbackCertificate) newData.fallbackCertificate = fallbackCertificate;
-
- domaindb.update(domain, newData, function (error) {
- if (error) return callback(error);
-
- if (!fallbackCertificate) return callback();
-
- reverseProxy.setFallbackCertificate(domain, fallbackCertificate, function (error) {
- if (error) return callback(error);
-
- eventlog.add(eventlog.ACTION_DOMAIN_UPDATE, auditSource, { domain, zoneName, provider });
-
- callback();
- });
- });
- });
- });
+ eventlog.add(eventlog.ACTION_DOMAIN_UPDATE, auditSource, { domain, zoneName, provider });
}
-function del(domain, auditSource, callback) {
+async function del(domain, auditSource) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof auditSource, 'object');
- assert.strictEqual(typeof callback, 'function');
- if (domain === settings.dashboardDomain()) return callback(new BoxError(BoxError.CONFLICT, 'Cannot remove admin domain'));
- if (domain === settings.mailDomain()) return callback(new BoxError(BoxError.CONFLICT, 'Cannot remove mail domain. Change the mail server location first'));
+ if (domain === settings.dashboardDomain()) throw new BoxError(BoxError.CONFLICT, 'Cannot remove admin domain');
+ if (domain === settings.mailDomain()) throw new BoxError(BoxError.CONFLICT, 'Cannot remove mail domain. Change the mail server location first');
- domaindb.del(domain, function (error) {
- if (error) return callback(error);
+ let queries = [
+ { query: 'DELETE FROM mail WHERE domain = ?', args: [ domain ] },
+ { query: 'DELETE FROM domains WHERE domain = ?', args: [ domain ] },
+ ];
- eventlog.add(eventlog.ACTION_DOMAIN_REMOVE, auditSource, { domain });
+ const [error, results] = await safe(database.transaction(queries));
+ if (error && error.code === 'ER_ROW_IS_REFERENCED_2') {
+ if (error.message.indexOf('apps_mailDomain_constraint') !== -1) throw new BoxError(BoxError.CONFLICT, 'Domain is in use by an app or the mailbox of an app. Check the domains of apps and the Email section of each app.');
+ if (error.message.indexOf('subdomains') !== -1) throw new BoxError(BoxError.CONFLICT, 'Domain is in use by one or more app(s).');
+ if (error.message.indexOf('mail') !== -1) throw new BoxError(BoxError.CONFLICT, 'Domain is in use by one or more mailboxes. Delete them first in the Email view.');
+ throw new BoxError(BoxError.CONFLICT, error.message);
+ }
+ if (error) throw error;
+ if (results[1].affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Domain not found');
- mail.onDomainRemoved(domain, NOOP_CALLBACK);
+ eventlog.add(eventlog.ACTION_DOMAIN_REMOVE, auditSource, { domain });
- return callback(null);
- });
+ mail.onDomainRemoved(domain, NOOP_CALLBACK);
}
-function clear(callback) {
- assert.strictEqual(typeof callback, 'function');
-
- domaindb.clear(function (error) {
- if (error) return callback(error);
-
- return callback(null);
- });
-}
-
-// returns the 'name' that needs to be inserted into zone
-// eslint-disable-next-line no-unused-vars
-function getName(domain, location, type) {
- const part = domain.domain.slice(0, -domain.zoneName.length - 1);
-
- if (location === '') return part;
-
- return part ? `${location}.${part}` : location;
-}
-
-function getDnsRecords(location, domain, type, callback) {
- assert.strictEqual(typeof location, 'string');
- assert.strictEqual(typeof domain, 'string');
- assert.strictEqual(typeof type, 'string');
- assert.strictEqual(typeof callback, 'function');
-
- get(domain, function (error, domainObject) {
- if (error) return callback(error);
-
- api(domainObject.provider).get(domainObject, location, type, function (error, values) {
- if (error) return callback(error);
-
- callback(null, values);
- });
- });
-}
-
-function checkDnsRecords(location, domain, callback) {
- assert.strictEqual(typeof location, 'string');
- assert.strictEqual(typeof domain, 'string');
- assert.strictEqual(typeof callback, 'function');
-
- getDnsRecords(location, domain, 'A', function (error, values) {
- if (error) return callback(error);
-
- sysinfo.getServerIp(function (error, ip) {
- if (error) return callback(error);
-
- if (values.length === 0) return callback(null, { needsOverwrite: false }); // does not exist
- if (values[0] === ip) return callback(null, { needsOverwrite: false }); // exists but in sync
-
- callback(null, { needsOverwrite: true });
- });
- });
-}
-
-// note: for TXT records the values must be quoted
-function upsertDnsRecords(location, domain, type, values, callback) {
- assert.strictEqual(typeof location, 'string');
- assert.strictEqual(typeof domain, 'string');
- assert.strictEqual(typeof type, 'string');
- assert(Array.isArray(values));
- assert.strictEqual(typeof callback, 'function');
-
- debug('upsertDNSRecord: %s on %s type %s values', location, domain, type, values);
-
- get(domain, function (error, domainObject) {
- if (error) return callback(error);
-
- api(domainObject.provider).upsert(domainObject, location, type, values, function (error) {
- if (error) return callback(error);
-
- callback(null);
- });
- });
-}
-
-function removeDnsRecords(location, domain, type, values, callback) {
- assert.strictEqual(typeof location, 'string');
- assert.strictEqual(typeof domain, 'string');
- assert.strictEqual(typeof type, 'string');
- assert(Array.isArray(values));
- assert.strictEqual(typeof callback, 'function');
-
- debug('removeDNSRecord: %s on %s type %s values', location, domain, type, values);
-
- get(domain, function (error, domainObject) {
- if (error) return callback(error);
-
- api(domainObject.provider).del(domainObject, location, type, values, function (error) {
- if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
-
- callback(null);
- });
- });
-}
-
-function waitForDnsRecord(location, domain, type, value, options, callback) {
- assert.strictEqual(typeof location, 'string');
- assert.strictEqual(typeof domain, 'string');
- assert(type === 'A' || type === 'TXT');
- assert.strictEqual(typeof value, 'string');
- assert(options && typeof options === 'object'); // { interval: 5000, times: 50000 }
- assert.strictEqual(typeof callback, 'function');
-
- get(domain, function (error, domainObject) {
- if (error) return callback(error);
-
- // linode DNS takes ~15mins
- if (!options.interval) options.interval = domainObject.provider === 'linode' ? 20000 : 5000;
-
- api(domainObject.provider).wait(domainObject, location, type, value, options, callback);
- });
+async function clear() {
+ await database.query('DELETE FROM domains');
}
// removes all fields that are strictly private and should never be returned by API calls
@@ -457,135 +305,3 @@ function removeRestrictedFields(domain) {
return result;
}
-
-function makeWildcard(vhost) {
- assert.strictEqual(typeof vhost, 'string');
-
- // if the vhost is like *.example.com, this function will do nothing
- let parts = vhost.split('.');
- parts[0] = '*';
- return parts.join('.');
-}
-
-function registerLocations(locations, options, progressCallback, callback) {
- assert(Array.isArray(locations));
- assert.strictEqual(typeof options, 'object');
- assert.strictEqual(typeof progressCallback, 'function');
- assert.strictEqual(typeof callback, 'function');
-
- debug(`registerLocations: Will register ${JSON.stringify(locations)} with options ${JSON.stringify(options)}`);
-
- const overwriteDns = options.overwriteDns || false;
-
- sysinfo.getServerIp(function (error, ip) {
- if (error) return callback(error);
-
- async.eachSeries(locations, function (location, iteratorDone) {
- async.retry({ times: 200, interval: 5000 }, function (retryCallback) {
- progressCallback({ message: `Registering location: ${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}` });
-
- // get the current record before updating it
- getDnsRecords(location.subdomain, location.domain, 'A', function (error, values) {
- if (error && error.reason === BoxError.EXTERNAL_ERROR) return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location })); // try again
- if (error && error.reason === BoxError.ACCESS_DENIED) return retryCallback(null, new BoxError(BoxError.ACCESS_DENIED, error.message, { domain: location }));
- if (error && error.reason === BoxError.NOT_FOUND) return retryCallback(null, new BoxError(BoxError.NOT_FOUND, error.message, { domain: location }));
- if (error) return retryCallback(null, new BoxError(BoxError.EXTERNAL_ERROR, error.message, location)); // give up for other errors
-
- if (values.length !== 0 && values[0] === ip) return retryCallback(null); // up-to-date
-
- // refuse to update any existing DNS record for custom domains that we did not create
- if (values.length !== 0 && !overwriteDns) return retryCallback(null, new BoxError(BoxError.ALREADY_EXISTS, 'DNS Record already exists', { domain: location }));
-
- upsertDnsRecords(location.subdomain, location.domain, 'A', [ ip ], function (error) {
- if (error && (error.reason === BoxError.BUSY || error.reason === BoxError.EXTERNAL_ERROR)) {
- progressCallback({ message: `registerSubdomains: Upsert error. Will retry. ${error.message}` });
- return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location })); // try again
- }
-
- retryCallback(null, error ? new BoxError(BoxError.EXTERNAL_ERROR, error.message, location) : null);
- });
- });
- }, function (error, result) {
- if (error || result) return iteratorDone(error || result);
-
- iteratorDone(null);
- });
- }, callback);
- });
-}
-
-function unregisterLocations(locations, progressCallback, callback) {
- assert(Array.isArray(locations));
- assert.strictEqual(typeof progressCallback, 'function');
- assert.strictEqual(typeof callback, 'function');
-
- sysinfo.getServerIp(function (error, ip) {
- if (error) return callback(error);
-
- async.eachSeries(locations, function (location, iteratorDone) {
- async.retry({ times: 30, interval: 5000 }, function (retryCallback) {
- progressCallback({ message: `Unregistering location: ${location.subdomain ? (location.subdomain + '.') : ''}${location.domain}` });
-
- removeDnsRecords(location.subdomain, location.domain, 'A', [ ip ], function (error) {
- if (error && error.reason === BoxError.NOT_FOUND) return retryCallback(null, null);
- if (error && (error.reason === BoxError.SBUSY || error.reason === BoxError.EXTERNAL_ERROR)) {
- progressCallback({ message: `Error unregistering location. Will retry. ${error.message}`});
- return retryCallback(new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location })); // try again
- }
-
- retryCallback(null, error ? new BoxError(BoxError.EXTERNAL_ERROR, error.message, { domain: location }) : null);
- });
- }, function (error, result) {
- if (error || result) return iteratorDone(error || result);
-
- iteratorDone();
- });
- }, callback);
- });
-}
-
-function syncDnsRecords(options, progressCallback, callback) {
- assert.strictEqual(typeof options, 'object');
- assert.strictEqual(typeof progressCallback, 'function');
- assert.strictEqual(typeof callback, 'function');
-
- if (options.domain && options.type === 'mail') return mail.setDnsRecords(options.domain, callback);
-
- getAll(function (error, domains) {
- if (error) return callback(error);
-
- if (options.domain) domains = domains.filter(d => d.domain === options.domain);
-
- const mailSubdomain = settings.mailFqdn().substr(0, settings.mailFqdn().length - settings.mailDomain().length - 1);
-
- apps.getAll(function (error, allApps) {
- if (error) return callback(error);
-
- let progress = 1, errors = [];
-
- // we sync by domain only to get some nice progress
- async.eachSeries(domains, function (domain, iteratorDone) {
- progressCallback({ percent: progress, message: `Updating DNS of ${domain.domain}`});
- progress += Math.round(100/(1+domains.length));
-
- let locations = [];
- if (domain.domain === settings.dashboardDomain()) locations.push({ subdomain: constants.DASHBOARD_LOCATION, domain: settings.dashboardDomain() });
- if (domain.domain === settings.mailDomain() && settings.mailFqdn() !== settings.dashboardFqdn()) locations.push({ subdomain: mailSubdomain, domain: settings.mailDomain() });
-
- allApps.forEach(function (app) {
- const appLocations = [{ subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains);
- locations = locations.concat(appLocations.filter(al => al.domain === domain.domain));
- });
-
- async.series([
- registerLocations.bind(null, locations, { overwriteDns: true }, progressCallback),
- progressCallback.bind(null, { message: `Updating mail DNS of ${domain.domain}`}),
- mail.setDnsRecords.bind(null, domain.domain)
- ], function (error) {
- if (error) errors.push({ domain: domain.domain, message: error.message });
- iteratorDone();
- });
- }, () => callback(null, { errors }));
- });
- });
-}
diff --git a/src/dyndns.js b/src/dyndns.js
index 5885b4e55..a686181ad 100644
--- a/src/dyndns.js
+++ b/src/dyndns.js
@@ -9,7 +9,7 @@ let apps = require('./apps.js'),
async = require('async'),
constants = require('./constants.js'),
debug = require('debug')('box:dyndns'),
- domains = require('./domains.js'),
+ dns = require('./dns.js'),
eventlog = require('./eventlog.js'),
paths = require('./paths.js'),
safe = require('safetydance'),
@@ -32,7 +32,7 @@ function sync(auditSource, callback) {
debug(`refreshDNS: updating ip from ${info.ip} to ${ip}`);
- domains.upsertDnsRecords(constants.DASHBOARD_LOCATION, settings.dashboardDomain(), 'A', [ ip ], function (error) {
+ dns.upsertDnsRecords(constants.DASHBOARD_LOCATION, settings.dashboardDomain(), 'A', [ ip ], function (error) {
if (error) return callback(error);
debug('refreshDNS: updated admin location');
@@ -44,7 +44,7 @@ function sync(auditSource, callback) {
// do not change state of installing apps since apptask will error if dns record already exists
if (app.installationState !== apps.ISTATE_INSTALLED) return callback();
- domains.upsertDnsRecords(app.location, app.domain, 'A', [ ip ], callback);
+ dns.upsertDnsRecords(app.location, app.domain, 'A', [ ip ], callback);
}, function (error) {
if (error) return callback(error);
diff --git a/src/mail.js b/src/mail.js
index 6d4a23331..a7d07ec41 100644
--- a/src/mail.js
+++ b/src/mail.js
@@ -69,7 +69,7 @@ const assert = require('assert'),
constants = require('./constants.js'),
database = require('./database.js'),
debug = require('debug')('box:mail'),
- dns = require('./native-dns.js'),
+ dns = require('./dns.js'),
docker = require('./docker.js'),
domains = require('./domains.js'),
eventlog = require('./eventlog.js'),
@@ -102,6 +102,9 @@ const REMOVE_MAILBOX = path.join(__dirname, 'scripts/rmmailbox.sh');
const MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector', 'bannerJson' ].join(',');
+const domainsGet = util.callbackify(domains.get),
+ domainsList = util.callbackify(domains.list);
+
function postProcess(data) {
data.enabled = !!data.enabled; // int to boolean
data.mailFromValidation = !!data.mailFromValidation; // int to boolean
@@ -544,7 +547,7 @@ function checkConfiguration(callback) {
let messages = {};
- domains.getAll(function (error, allDomains) {
+ domainsList(function (error, allDomains) {
if (error) return callback(error);
async.eachSeries(allDomains, function (domainObject, iteratorCallback) {
@@ -810,7 +813,7 @@ function txtRecordsWithSpf(domain, mailFqdn, callback) {
assert.strictEqual(typeof mailFqdn, 'string');
assert.strictEqual(typeof callback, 'function');
- domains.getDnsRecords('', domain, 'TXT', function (error, txtRecords) {
+ dns.getDnsRecords('', domain, 'TXT', function (error, txtRecords) {
if (error) return callback(error);
debug('txtRecordsWithSpf: current txt records - %j', txtRecords);
@@ -927,7 +930,7 @@ function upsertDnsRecords(domain, mailFqdn, callback) {
if (txtRecords) records.push({ subdomain: '', domain: domain, type: 'TXT', values: txtRecords });
- domains.getDnsRecords('_dmarc', domain, 'TXT', function (error, dmarcRecords) { // only update dmarc if absent. this allows user to set email for reporting
+ dns.getDnsRecords('_dmarc', domain, 'TXT', function (error, dmarcRecords) { // only update dmarc if absent. this allows user to set email for reporting
if (error) return callback(error);
if (dmarcRecords.length === 0) records.push({ subdomain: '_dmarc', domain: domain, type: 'TXT', values: [ '"v=DMARC1; p=reject; pct=100"' ] });
@@ -935,7 +938,7 @@ function upsertDnsRecords(domain, mailFqdn, callback) {
debug('upsertDnsRecords: will update %j', records);
async.mapSeries(records, function (record, iteratorCallback) {
- domains.upsertDnsRecords(record.subdomain, record.domain, record.type, record.values, iteratorCallback);
+ dns.upsertDnsRecords(record.subdomain, record.domain, record.type, record.values, iteratorCallback);
}, function (error, changeIds) {
if (error) {
debug(`upsertDnsRecords: failed to update: ${error}`);
@@ -981,7 +984,7 @@ function changeLocation(auditSource, progressCallback, callback) {
cloudron.setupDnsAndCert(subdomain, domain, auditSource, progressCallback, function (error) {
if (error) return callback(error);
- domains.getAll(function (error, allDomains) {
+ domainsList(function (error, allDomains) {
if (error) return callback(error);
async.eachOfSeries(allDomains, function (domainObject, idx, iteratorDone) {
@@ -1010,10 +1013,10 @@ function setLocation(subdomain, domain, auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
- domains.get(domain, function (error, domainObject) {
+ domainsGet(domain, function (error, domainObject) {
if (error) return callback(error);
- const fqdn = domains.fqdn(subdomain, domainObject);
+ const fqdn = dns.fqdn(subdomain, domainObject);
settings.setMailLocation(domain, fqdn, async function (error) {
if (error) return callback(error);
diff --git a/src/native-dns.js b/src/native-dns.js
deleted file mode 100644
index 2ae7464b3..000000000
--- a/src/native-dns.js
+++ /dev/null
@@ -1,43 +0,0 @@
-'use strict';
-
-exports = module.exports = {
- resolve
-};
-
-var assert = require('assert'),
- constants = require('./constants.js'),
- dns = require('dns'),
- _ = require('underscore');
-
-const DEFAULT_OPTIONS = { server: '127.0.0.1', timeout: 5000 }; // unbound runs on 127.0.0.1
-
-// a note on TXT records. It doesn't have quotes ("") at the DNS level. Those quotes
-// are added for DNS server software to enclose spaces. Such quotes may also be returned
-// by the DNS REST API of some providers
-function resolve(hostname, rrtype, options, callback) {
- assert.strictEqual(typeof hostname, 'string');
- assert.strictEqual(typeof rrtype, 'string');
- assert(options && typeof options === 'object');
- assert.strictEqual(typeof callback, 'function');
-
- const resolver = new dns.Resolver();
- options = _.extend({ }, DEFAULT_OPTIONS, options);
-
- // Only use unbound on a Cloudron
- if (constants.CLOUDRON) resolver.setServers([ options.server ]);
-
- // should callback with ECANCELLED but looks like we might hit https://github.com/nodejs/node/issues/14814
- const timerId = setTimeout(resolver.cancel.bind(resolver), options.timeout || 5000);
-
- resolver.resolve(hostname, rrtype, function (error, result) {
- clearTimeout(timerId);
-
- if (error && error.code === 'ECANCELLED') error.code = 'TIMEOUT';
-
- // result is an empty array if there was no error but there is no record. when you query a random
- // domain, it errors with ENOTFOUND. But if you query an existing domain (A record) but with different
- // type (CNAME) it is not an error and empty array
- // for TXT records, result is 2d array of strings
- callback(error, result);
- });
-}
diff --git a/src/reverseproxy.js b/src/reverseproxy.js
index 8c97d444e..c645d2bad 100644
--- a/src/reverseproxy.js
+++ b/src/reverseproxy.js
@@ -38,6 +38,7 @@ const acme2 = require('./acme2.js'),
constants = require('./constants.js'),
crypto = require('crypto'),
debug = require('debug')('box:reverseproxy'),
+ dns = require('./dns.js'),
domains = require('./domains.js'),
ejs = require('ejs'),
eventlog = require('./eventlog.js'),
@@ -57,6 +58,8 @@ const acme2 = require('./acme2.js'),
const NGINX_APPCONFIG_EJS = fs.readFileSync(__dirname + '/nginxconfig.ejs', { encoding: 'utf8' });
const RESTART_SERVICE_CMD = path.join(__dirname, 'scripts/restartservice.sh');
+const domainsGet = util.callbackify(domains.get),
+ domainsList = util.callbackify(domains.list);
function nginxLocation(s) {
if (!s.startsWith('!')) return s;
@@ -159,7 +162,7 @@ function validateCertificate(location, domainObject, certificate) {
if (cert && !key) return new BoxError(BoxError.BAD_FIELD, 'missing key', { field: 'key' });
// -checkhost checks for SAN or CN exclusively. SAN takes precedence and if present, ignores the CN.
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
let result = safe.child_process.execSync(`openssl x509 -noout -checkhost "${fqdn}"`, { encoding: 'utf8', input: cert });
if (result === null) return new BoxError(BoxError.BAD_FIELD, 'Unable to get certificate subject:' + safe.error.message, { field: 'cert' });
@@ -224,24 +227,23 @@ function generateFallbackCertificateSync(domain) {
return { cert: cert, key: key, error: null };
}
-function setFallbackCertificate(domain, fallback, callback) {
+async function setFallbackCertificate(domain, fallback) {
assert.strictEqual(typeof domain, 'string');
assert(fallback && typeof fallback === 'object');
assert.strictEqual(typeof fallback, 'object');
- assert.strictEqual(typeof callback, 'function');
debug(`setFallbackCertificate: setting certs for domain ${domain}`);
- if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.cert`), fallback.cert)) return callback(new BoxError(BoxError.FS_ERROR, safe.error.message));
- if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.key`), fallback.key)) return callback(new BoxError(BoxError.FS_ERROR, safe.error.message));
+ if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.cert`), fallback.cert)) throw new BoxError(BoxError.FS_ERROR, safe.error.message);
+ if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.key`), fallback.key)) throw new BoxError(BoxError.FS_ERROR, safe.error.message);
// TODO: maybe the cert is being used by the mail container
- reload(callback);
+ await util.promisify(reload)();
}
function restoreFallbackCertificates(callback) {
assert.strictEqual(typeof callback, 'function');
- domains.getAll(function (error, result) {
+ domainsList(function (error, result) {
if (error) return callback(error);
result.forEach(function (domain) {
@@ -278,7 +280,7 @@ function getAcmeCertificatePathSync(vhost, domainObject) {
let certName, certFilePath, keyFilePath, csrFilePath, acmeChallengesDir = paths.ACME_CHALLENGES_DIR;
if (vhost !== domainObject.domain && domainObject.tlsConfig.wildcard) { // bare domain is not part of wildcard SAN
- certName = domains.makeWildcard(vhost).replace('*.', '_.');
+ certName = dns.makeWildcard(vhost).replace('*.', '_.');
certFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.cert`);
keyFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.key`);
csrFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.csr`);
@@ -298,7 +300,7 @@ function setAppCertificate(location, domainObject, certificate, callback) {
assert.strictEqual(typeof certificate, 'object');
assert.strictEqual(typeof callback, 'function');
- const fqdn = domains.fqdn(location, domainObject);
+ const fqdn = dns.fqdn(location, domainObject);
const { certFilePath, keyFilePath } = getAppCertificatePathSync(fqdn);
if (certificate.cert && certificate.key) {
@@ -321,7 +323,7 @@ function getCertificatePath(fqdn, domain, callback) {
// 2. if using fallback provider, return that cert
// 3. look for LE certs
- domains.get(domain, function (error, domainObject) {
+ domainsGet(domain, function (error, domainObject) {
if (error) return callback(error);
const appCertPath = getAppCertificatePathSync(fqdn); // user cert always wins
@@ -398,7 +400,7 @@ function ensureCertificate(vhost, domain, auditSource, callback) {
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
- domains.get(domain, async function (error, domainObject) {
+ domainsGet(domain, async function (error, domainObject) {
if (error) return callback(error);
let bundle;
@@ -482,10 +484,10 @@ function writeDashboardConfig(domain, callback) {
debug(`writeDashboardConfig: writing admin config for ${domain}`);
- domains.get(domain, function (error, domainObject) {
+ domainsGet(domain, function (error, domainObject) {
if (error) return callback(error);
- const dashboardFqdn = domains.fqdn(constants.DASHBOARD_LOCATION, domainObject);
+ const dashboardFqdn = dns.fqdn(constants.DASHBOARD_LOCATION, domainObject);
getCertificatePath(dashboardFqdn, domainObject.domain, function (error, bundle) {
if (error) return callback(error);
diff --git a/src/routes/domains.js b/src/routes/domains.js
index 7f669b049..c1052edbe 100644
--- a/src/routes/domains.js
+++ b/src/routes/domains.js
@@ -3,7 +3,7 @@
exports = module.exports = {
add,
get,
- getAll,
+ list,
update,
del,
@@ -13,11 +13,13 @@ exports = module.exports = {
const assert = require('assert'),
auditSource = require('../auditsource.js'),
BoxError = require('../boxerror.js'),
+ dns = require('../dns.js'),
domains = require('../domains.js'),
HttpError = require('connect-lastmile').HttpError,
- HttpSuccess = require('connect-lastmile').HttpSuccess;
+ HttpSuccess = require('connect-lastmile').HttpSuccess,
+ safe = require('safetydance');
-function add(req, res, next) {
+async function add(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
@@ -50,34 +52,31 @@ function add(req, res, next) {
tlsConfig: req.body.tlsConfig || { provider: 'letsencrypt-prod' }
};
- domains.add(req.body.domain, data, auditSource.fromRequest(req), function (error) {
- if (error) return next(BoxError.toHttpError(error));
+ const [error] = await safe(domains.add(req.body.domain, data, auditSource.fromRequest(req)));
+ if (error) return next(BoxError.toHttpError(error));
- next(new HttpSuccess(201, {}));
- });
+ next(new HttpSuccess(201, {}));
}
-function get(req, res, next) {
+async function get(req, res, next) {
assert.strictEqual(typeof req.params.domain, 'string');
- domains.get(req.params.domain, function (error, result) {
- if (error) return next(BoxError.toHttpError(error));
+ const [error, result] = await safe(domains.get(req.params.domain));
+ if (error) return next(BoxError.toHttpError(error));
+ if (!result) return next(new HttpError(404, 'Domain not found'));
- next(new HttpSuccess(200, domains.removePrivateFields(result)));
- });
+ next(new HttpSuccess(200, domains.removePrivateFields(result)));
}
-function getAll(req, res, next) {
- domains.getAll(function (error, result) {
- if (error) return next(new HttpError(500, error));
+async function list(req, res, next) {
+ let [error, results] = await safe(domains.list());
+ if (error) return next(new HttpError(500, error));
- result = result.map(domains.removeRestrictedFields);
-
- next(new HttpSuccess(200, { domains: result }));
- });
+ results = results.map(domains.removeRestrictedFields);
+ next(new HttpSuccess(200, { domains: results }));
}
-function update(req, res, next) {
+async function update(req, res, next) {
assert.strictEqual(typeof req.params.domain, 'string');
assert.strictEqual(typeof req.body, 'object');
@@ -118,21 +117,19 @@ function update(req, res, next) {
wellKnown: req.body.wellKnown || null
};
- domains.update(req.params.domain, data, auditSource.fromRequest(req), function (error) {
- if (error) return next(BoxError.toHttpError(error));
+ const [error] = await safe(domains.update(req.params.domain, data, auditSource.fromRequest(req)));
+ if (error) return next(BoxError.toHttpError(error));
- next(new HttpSuccess(204, {}));
- });
+ next(new HttpSuccess(204, {}));
}
-function del(req, res, next) {
+async function del(req, res, next) {
assert.strictEqual(typeof req.params.domain, 'string');
- domains.del(req.params.domain, auditSource.fromRequest(req), function (error) {
- if (error) return next(BoxError.toHttpError(error));
+ const [error] = await safe(domains.del(req.params.domain, auditSource.fromRequest(req)));
+ if (error) return next(BoxError.toHttpError(error));
- next(new HttpSuccess(204));
- });
+ next(new HttpSuccess(204));
}
function checkDnsRecords(req, res, next) {
@@ -143,7 +140,7 @@ function checkDnsRecords(req, res, next) {
// some DNS providers like DigitalOcean take a really long time to verify credentials (https://github.com/expressjs/timeout/issues/26)
req.clearTimeout();
- domains.checkDnsRecords(req.query.subdomain, req.params.domain, function (error, result) {
+ dns.checkDnsRecords(req.query.subdomain, req.params.domain, function (error, result) {
if (error && error.reason === BoxError.ACCESS_DENIED) return next(new HttpSuccess(200, { error: { reason: error.reason, message: error.message }}));
if (error) return next(BoxError.toHttpError(error));
diff --git a/src/routes/test/mail-test.js b/src/routes/test/mail-test.js
index 7822ac4cc..b53be74ed 100644
--- a/src/routes/test/mail-test.js
+++ b/src/routes/test/mail-test.js
@@ -48,7 +48,7 @@ describe('Mail API', function () {
let dkimDomain, spfDomain, mxDomain, dmarcDomain;
before(function (done) {
- const dns = require('../../native-dns.js');
+ const dns = require('../../dns.js');
// replace dns resolveTxt()
resolve = dns.resolve;
@@ -79,7 +79,7 @@ describe('Mail API', function () {
});
after(function (done) {
- var dns = require('../../native-dns.js');
+ const dns = require('../../dns.js');
dns.resolve = resolve;
diff --git a/src/routes/test/tasks-test.js b/src/routes/test/tasks-test.js
index f3a02dfda..0b944706c 100644
--- a/src/routes/test/tasks-test.js
+++ b/src/routes/test/tasks-test.js
@@ -1,6 +1,5 @@
'use strict';
-const { resolve } = require('../../native-dns.js');
/* global it:false */
/* global describe:false */
/* global before:false */
diff --git a/src/server.js b/src/server.js
index ca32efdca..00747b689 100644
--- a/src/server.js
+++ b/src/server.js
@@ -297,7 +297,7 @@ function initializeExpressSync() {
// domain routes
router.post('/api/v1/domains', json, token, authorizeAdmin, routes.domains.add);
- router.get ('/api/v1/domains', token, routes.domains.getAll);
+ router.get ('/api/v1/domains', token, routes.domains.list);
router.get ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.get); // this is manage scope because it returns non-restricted fields
router.put ('/api/v1/domains/:domain', json, token, authorizeAdmin, routes.domains.update);
router.del ('/api/v1/domains/:domain', token, authorizeAdmin, routes.domains.del);
diff --git a/src/taskworker.js b/src/taskworker.js
index d151fba60..0e2ef7b85 100755
--- a/src/taskworker.js
+++ b/src/taskworker.js
@@ -8,7 +8,7 @@ const apptask = require('./apptask.js'),
backuptask = require('./backuptask.js'),
cloudron = require('./cloudron.js'),
database = require('./database.js'),
- domains = require('./domains.js'),
+ dns = require('./dns.js'),
externalLdap = require('./externalldap.js'),
fs = require('fs'),
mail = require('./mail.js'),
@@ -27,7 +27,7 @@ const TASKS = { // indexed by task type
cleanBackups: backupCleaner.run,
syncExternalLdap: externalLdap.sync,
changeMailLocation: mail.changeLocation,
- syncDnsRecords: domains.syncDnsRecords,
+ syncDnsRecords: dns.syncDnsRecords,
_identity: (arg, progressCallback, callback) => callback(null, arg),
_error: (arg, progressCallback, callback) => callback(new Error(`Failed for arg: ${arg}`)),
diff --git a/src/test/apptask-test.js b/src/test/apptask-test.js
index 79db9997f..dcdb5c800 100644
--- a/src/test/apptask-test.js
+++ b/src/test/apptask-test.js
@@ -12,40 +12,40 @@ const apptask = require('../apptask.js'),
paths = require('../paths.js'),
_ = require('underscore');
-const { APP } = common;
-
describe('apptask', function () {
- before(common.setup);
- after(common.cleanup);
+ const { setup, cleanup, app } = common;
+
+ before(setup);
+ after(cleanup);
it('create volume', function (done) {
- apptask._createAppDir(APP, function (error) {
- expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + APP.id)).to.be(true);
- expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + APP.id + '/data')).to.be(false);
+ apptask._createAppDir(app, function (error) {
+ expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true);
+ expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id + '/data')).to.be(false);
expect(error).to.be(null);
done();
});
});
it('delete volume - removeDirectory (false) ', function (done) {
- apptask._deleteAppDir(APP, { removeDirectory: false }, function (error) {
- expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + APP.id)).to.be(true);
- expect(fs.readdirSync(paths.APPS_DATA_DIR + '/' + APP.id).length).to.be(0); // empty
+ apptask._deleteAppDir(app, { removeDirectory: false }, function (error) {
+ expect(fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true);
+ expect(fs.readdirSync(paths.APPS_DATA_DIR + '/' + app.id).length).to.be(0); // empty
expect(error).to.be(null);
done();
});
});
it('delete volume - removeDirectory (true) ', function (done) {
- apptask._deleteAppDir(APP, { removeDirectory: true }, function (error) {
- expect(!fs.existsSync(paths.APPS_DATA_DIR + '/' + APP.id)).to.be(true);
+ apptask._deleteAppDir(app, { removeDirectory: true }, function (error) {
+ expect(!fs.existsSync(paths.APPS_DATA_DIR + '/' + app.id)).to.be(true);
expect(error).to.be(null);
done();
});
});
it('barfs on empty manifest', function (done) {
- var badApp = _.extend({ }, APP);
+ var badApp = _.extend({ }, app);
badApp.manifest = { };
apptask._verifyManifest(badApp.manifest, function (error) {
@@ -55,8 +55,8 @@ describe('apptask', function () {
});
it('fails on bad manifest', function (done) {
- var badApp = _.extend({ }, APP);
- badApp.manifest = _.extend({ }, APP.manifest);
+ var badApp = _.extend({ }, app);
+ badApp.manifest = _.extend({ }, app.manifest);
delete badApp.manifest.httpPort;
apptask._verifyManifest(badApp.manifest, function (error) {
@@ -66,8 +66,8 @@ describe('apptask', function () {
});
it('barfs on incompatible manifest', function (done) {
- var badApp = _.extend({ }, APP);
- badApp.manifest = _.extend({ }, APP.manifest);
+ var badApp = _.extend({ }, app);
+ badApp.manifest = _.extend({ }, app.manifest);
badApp.manifest.maxBoxVersion = '0.0.0'; // max box version is too small
apptask._verifyManifest(badApp.manifest, function (error) {
@@ -77,7 +77,7 @@ describe('apptask', function () {
});
it('verifies manifest', function (done) {
- var goodApp = _.extend({ }, APP);
+ var goodApp = _.extend({ }, app);
apptask._verifyManifest(goodApp.manifest, function (error) {
expect(error).to.be(null);
diff --git a/src/test/common.js b/src/test/common.js
index 434a2a36a..79141b842 100644
--- a/src/test/common.js
+++ b/src/test/common.js
@@ -116,19 +116,20 @@ const app = {
exports = module.exports = {
createTree,
domainSetup,
+ databaseSetup,
setup,
cleanup,
checkMails,
clearMailQueue,
mockApiServerOrigin: 'http://localhost:6060',
- dashboardDomain: 'test.example.com',
- dashboardFqdn: 'my.test.example.com',
+ dashboardDomain: domain.domain,
+ dashboardFqdn: `my.${domain.domain}`,
app,
admin,
auditSource,
- domain,
+ domain, // the domain object
manifest,
user,
appstoreToken: 'atoken',
@@ -161,7 +162,7 @@ function createTree(root, obj) {
createSubTree(obj, root);
}
-function domainSetup(done) {
+function databaseSetup(done) {
nock.cleanAll();
async.series([
@@ -171,6 +172,14 @@ function domainSetup(done) {
settings.setDashboardLocation.bind(null, exports.dashboardDomain, exports.dashboardFqdn),
settings.initCache,
blobs.initSecrets,
+ ], done);
+}
+
+function domainSetup(done) {
+ nock.cleanAll();
+
+ async.series([
+ databaseSetup,
domains.add.bind(null, domain.domain, domain, auditSource),
], done);
}
diff --git a/src/test/database-test.js b/src/test/database-test.js
index 8c661fa73..a8782121b 100644
--- a/src/test/database-test.js
+++ b/src/test/database-test.js
@@ -10,7 +10,7 @@ const appdb = require('../appdb.js'),
async = require('async'),
BoxError = require('../boxerror.js'),
database = require('../database'),
- domaindb = require('../domaindb'),
+ domains = require('../domains.js'),
expect = require('expect.js'),
mailboxdb = require('../mailboxdb.js'),
reverseProxy = require('../reverseproxy.js'),
@@ -27,6 +27,8 @@ const DOMAIN_0 = {
};
DOMAIN_0.fallbackCertificate = reverseProxy.generateFallbackCertificateSync(DOMAIN_0.domain);
+const auditSource = { ip: '1.2.3.4' };
+
const DOMAIN_1 = {
domain: 'foo.cloudron.io',
zoneName: 'cloudron.io',
@@ -52,158 +54,6 @@ describe('database', function () {
], done);
});
- describe('domains', function () {
- after(function (done) {
- database._clear(done);
- });
-
- it('can add domain', function (done) {
- domaindb.add(DOMAIN_0.domain, DOMAIN_0, done);
- });
-
- it('can add another domain', function (done) {
- domaindb.add(DOMAIN_1.domain, DOMAIN_1, done);
- });
-
- it('cannot add same domain twice', function (done) {
- domaindb.add(DOMAIN_0.domain, DOMAIN_0, function (error) {
- expect(error).to.be.ok();
- expect(error.reason).to.be(BoxError.ALREADY_EXISTS);
- done();
- });
- });
-
- it('can get domain', function (done) {
- domaindb.get(DOMAIN_0.domain, function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.an('object');
- expect(result.domain).to.equal(DOMAIN_0.domain);
- expect(result.zoneName).to.equal(DOMAIN_0.zoneName);
- expect(result.config).to.eql(DOMAIN_0.config);
-
- done();
- });
- });
-
- it('can update domain', function (done) {
- const newConfig = { provider: 'manual' };
- const newTlsConfig = { provider: 'foobar' };
-
- domaindb.update(DOMAIN_1.domain, { provider: DOMAIN_1.provider, config: newConfig, tlsConfig: newTlsConfig }, function (error) {
- expect(error).to.equal(null);
-
- domaindb.get(DOMAIN_1.domain, function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.an('object');
- expect(result.domain).to.equal(DOMAIN_1.domain);
- expect(result.zoneName).to.equal(DOMAIN_1.zoneName);
- expect(result.provider).to.equal(DOMAIN_1.provider);
- expect(result.config).to.eql(newConfig);
- expect(result.tlsConfig).to.eql(newTlsConfig);
-
- DOMAIN_1.config = newConfig;
- DOMAIN_1.tlsConfig = newTlsConfig;
-
- done();
- });
- });
- });
-
- it('can get all domains', function (done) {
- domaindb.getAll(function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.an('array');
- expect(result.length).to.equal(2);
-
- // sorted by domain
- expect(result[0].domain).to.equal(DOMAIN_1.domain);
- expect(result[0].zoneName).to.equal(DOMAIN_1.zoneName);
- expect(result[0].provider).to.equal(DOMAIN_1.provider);
- expect(result[0].config).to.eql(DOMAIN_1.config);
- expect(result[0].tlsConfig).to.eql(DOMAIN_1.tlsConfig);
-
- expect(result[1].domain).to.equal(DOMAIN_0.domain);
- expect(result[1].zoneName).to.equal(DOMAIN_0.zoneName);
- expect(result[1].provider).to.equal(DOMAIN_0.provider);
- expect(result[1].config).to.eql(DOMAIN_0.config);
- expect(result[1].tlsConfig).to.eql(DOMAIN_0.tlsConfig);
-
- done();
- });
- });
-
- it('cannot delete non-existing domain', function (done) {
- domaindb.del('not.exists', function (error) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.equal(BoxError.NOT_FOUND);
-
- done();
- });
- });
-
- var APP_0 = {
- id: 'appid-0',
- appStoreId: 'appStoreId-0',
- installationState: apps.ISTATE_PENDING_INSTALL,
- error: null,
- runState: 'running',
- location: 'some-location-0',
- domain: DOMAIN_0.domain,
- manifest: { version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0' },
- containerId: null,
- containerIp: null,
- portBindings: { port: { hostPort: 5678, type: 'tcp' } },
- health: null,
- accessRestriction: null,
- lastBackupId: null,
- memoryLimit: 4294967296,
- cpuShares: 1024,
- sso: true,
- debugMode: null,
- reverseProxyConfig: {},
- enableBackup: true,
- env: {},
- mailboxName: 'talktome',
- mailboxDomain: DOMAIN_0.domain,
- enableAutomaticUpdate: true,
- dataDir: null,
- tags: [],
- label: null,
- taskId: null,
- mounts: [],
- proxyAuth: false,
- servicesConfig: {},
- hasIcon: false,
- hasAppStoreIcon: false
- };
-
- it('cannot delete referenced domain', function (done) {
- appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0, function (error) {
- expect(error).to.be(null);
-
- domaindb.del(DOMAIN_0.domain, function (error) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.equal(BoxError.CONFLICT);
-
- appdb.del(APP_0.id, done);
- });
- });
- });
-
- it('can delete existing domain', function (done) {
- domaindb.del(DOMAIN_0.domain, function (error) {
- expect(error).to.be(null);
-
- domaindb.get(DOMAIN_0.domain, function (error) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.equal(BoxError.NOT_FOUND);
-
- done();
- });
- });
- });
- });
-
describe('apps', function () {
var APP_0 = {
id: 'appid-0',
@@ -286,7 +136,7 @@ describe('database', function () {
before(function (done) {
async.series([
database._clear,
- domaindb.add.bind(null, DOMAIN_0.domain, DOMAIN_0)
+ domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, auditSource)
], done);
});
@@ -626,7 +476,7 @@ describe('database', function () {
describe('mailboxes', function () {
before(function (done) {
async.series([
- domaindb.add.bind(null, DOMAIN_0.domain, DOMAIN_0),
+ domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0),
], done);
});
diff --git a/src/test/dns-providers-test.js b/src/test/dns-providers-test.js
new file mode 100644
index 000000000..377216e15
--- /dev/null
+++ b/src/test/dns-providers-test.js
@@ -0,0 +1,1269 @@
+/* jslint node:true */
+/* global it:false */
+/* global describe:false */
+/* global before:false */
+/* global beforeEach:false */
+/* global after:false */
+
+'use strict';
+
+const AWS = require('aws-sdk'),
+ common = require('./common.js'),
+ dns = require('../dns.js'),
+ domains = require('../domains.js'),
+ expect = require('expect.js'),
+ GCDNS = require('@google-cloud/dns').DNS,
+ nock = require('nock'),
+ _ = require('underscore');
+
+describe('dns provider', function () {
+ const { setup, cleanup, auditSource, domain } = common;
+ const domainCopy = Object.assign({}, domain); // make a copy
+
+ before(setup);
+ after(cleanup);
+
+ describe('noop', function () {
+ before(async function () {
+ domainCopy.provider = 'noop';
+ domainCopy.config = {};
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ it('upsert succeeds', function (done) {
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+
+ done();
+ });
+ });
+
+ it('get succeeds', function (done) {
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+
+ done();
+ });
+ });
+ });
+
+ describe('digitalocean', function () {
+ let TOKEN = 'sometoken';
+ let DIGITALOCEAN_ENDPOINT = 'https://api.digitalocean.com';
+
+ before(async function () {
+ domainCopy.provider = 'digitalocean';
+ domainCopy.config = {
+ token: TOKEN
+ };
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ it('upsert non-existing record succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ id: 3352892,
+ type: 'A',
+ name: '@',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .get('/v2/domains/' + domainCopy.zoneName + '/records')
+ .reply(200, { domain_records: [] });
+ let req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .post('/v2/domains/' + domainCopy.zoneName + '/records')
+ .reply(201, { domain_record: DOMAIN_RECORD_0 });
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('upsert existing record succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ id: 3352892,
+ type: 'A',
+ name: '@',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_1 = {
+ id: 3352893,
+ type: 'A',
+ name: 'test',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_1_NEW = {
+ id: 3352893,
+ type: 'A',
+ name: 'test',
+ data: '1.2.3.5',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .get('/v2/domains/' + domainCopy.zoneName + '/records')
+ .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1] });
+ let req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .put('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_1.id)
+ .reply(200, { domain_record: DOMAIN_RECORD_1_NEW });
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', [DOMAIN_RECORD_1_NEW.data], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('upsert multiple record succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ id: 3352892,
+ type: 'A',
+ name: '@',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_1 = {
+ id: 3352893,
+ type: 'TXT',
+ name: '@',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_1_NEW = {
+ id: 3352893,
+ type: 'TXT',
+ name: '@',
+ data: 'somethingnew',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_2 = {
+ id: 3352894,
+ type: 'TXT',
+ name: '@',
+ data: 'something',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_2_NEW = {
+ id: 3352894,
+ type: 'TXT',
+ name: '@',
+ data: 'somethingnew',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_3_NEW = {
+ id: 3352895,
+ type: 'TXT',
+ name: '@',
+ data: 'thirdnewone',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .get('/v2/domains/' + domainCopy.zoneName + '/records')
+ .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1, DOMAIN_RECORD_2] });
+ let req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .put('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_1.id)
+ .reply(200, { domain_record: DOMAIN_RECORD_1_NEW });
+ let req3 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .put('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_2.id)
+ .reply(200, { domain_record: DOMAIN_RECORD_2_NEW });
+ let req4 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .post('/v2/domains/' + domainCopy.zoneName + '/records')
+ .reply(201, { domain_record: DOMAIN_RECORD_2_NEW });
+
+ dns.upsertDnsRecords('', domainCopy.domain, 'TXT', [DOMAIN_RECORD_2_NEW.data, DOMAIN_RECORD_1_NEW.data, DOMAIN_RECORD_3_NEW.data], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+ expect(req3.isDone()).to.be.ok();
+ expect(req4.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('get succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ id: 3352892,
+ type: 'A',
+ name: '@',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_1 = {
+ id: 3352893,
+ type: 'A',
+ name: 'test',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .get('/v2/domains/' + domainCopy.zoneName + '/records')
+ .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1] });
+
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(1);
+ expect(result[0]).to.eql(DOMAIN_RECORD_1.data);
+ expect(req1.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ id: 3352892,
+ type: 'A',
+ name: '@',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let DOMAIN_RECORD_1 = {
+ id: 3352893,
+ type: 'A',
+ name: 'test',
+ data: '1.2.3.4',
+ priority: null,
+ port: null,
+ weight: null
+ };
+
+ let req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .get('/v2/domains/' + domainCopy.zoneName + '/records')
+ .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1] });
+ let req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
+ .delete('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_1.id)
+ .reply(204, {});
+
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+ });
+
+ describe('godaddy', function () {
+ let KEY = 'somekey', SECRET = 'somesecret';
+ let GODADDY_API = 'https://api.godaddy.com/v1/domains';
+
+ before(async function () {
+ domainCopy.provider = 'godaddy';
+ domainCopy.config = {
+ apiKey: KEY,
+ apiSecret: SECRET
+ };
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ it('upsert record succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = [{
+ ttl: 600,
+ data: '1.2.3.4'
+ }];
+
+ let req1 = nock(GODADDY_API)
+ .put('/' + domainCopy.zoneName + '/records/A/test', DOMAIN_RECORD_0)
+ .reply(200, {});
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('get succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = [{
+ ttl: 600,
+ data: '1.2.3.4'
+ }];
+
+ let req1 = nock(GODADDY_API)
+ .get('/' + domainCopy.zoneName + '/records/A/test')
+ .reply(200, DOMAIN_RECORD_0);
+
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(1);
+ expect(result[0]).to.eql(DOMAIN_RECORD_0[0].data);
+ expect(req1.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = [{ // existing
+ ttl: 600,
+ data: '1.2.3.4'
+ }];
+
+ let DOMAIN_RECORD_1 = [{ // replaced
+ ttl: 600,
+ data: '0.0.0.0'
+ }];
+
+ let req1 = nock(GODADDY_API)
+ .get('/' + domainCopy.zoneName + '/records/A/test')
+ .reply(200, DOMAIN_RECORD_0);
+
+ let req2 = nock(GODADDY_API)
+ .put('/' + domainCopy.zoneName + '/records/A/test', DOMAIN_RECORD_1)
+ .reply(200, {});
+
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+ });
+
+ describe('gandi', function () {
+ let TOKEN = 'sometoken';
+ let GANDI_API = 'https://dns.api.gandi.net/api/v5';
+
+ before(async function () {
+ domainCopy.provider = 'gandi';
+ domainCopy.config = {
+ token: TOKEN
+ };
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ it('upsert record succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ 'rrset_ttl': 300,
+ 'rrset_values': ['1.2.3.4']
+ };
+
+ let req1 = nock(GANDI_API)
+ .put('/domains/' + domainCopy.zoneName + '/records/test/A', DOMAIN_RECORD_0)
+ .reply(201, { message: 'Zone Record Created' });
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('get succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ 'rrset_type': 'A',
+ 'rrset_ttl': 600,
+ 'rrset_name': 'test',
+ 'rrset_values': ['1.2.3.4']
+ };
+
+ let req1 = nock(GANDI_API)
+ .get('/domains/' + domainCopy.zoneName + '/records/test/A')
+ .reply(200, DOMAIN_RECORD_0);
+
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(1);
+ expect(result[0]).to.eql(DOMAIN_RECORD_0.rrset_values[0]);
+ expect(req1.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ nock.cleanAll();
+
+ let req2 = nock(GANDI_API)
+ .delete('/domains/' + domainCopy.zoneName + '/records/test/A')
+ .reply(204, {});
+
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+ });
+
+ describe('name.com', function () {
+ const TOKEN = 'sometoken';
+ const NAMECOM_API = 'https://api.name.com/v4';
+
+ before(async function () {
+ domainCopy.provider = 'namecom';
+ domainCopy.config = {
+ username: 'fake',
+ token: TOKEN
+ };
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ it('upsert record succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ host: 'test',
+ type: 'A',
+ answer: '1.2.3.4',
+ ttl: 300
+ };
+
+ let req1 = nock(NAMECOM_API)
+ .get(`/domains/${domainCopy.zoneName}/records`)
+ .reply(200, { records: [] });
+
+ let req2 = nock(NAMECOM_API)
+ .post(`/domains/${domainCopy.zoneName}/records`, DOMAIN_RECORD_0)
+ .reply(200, {});
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('get succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ host: 'test',
+ type: 'A',
+ answer: '1.2.3.4',
+ ttl: 300
+ };
+
+ let req1 = nock(NAMECOM_API)
+ .get(`/domains/${domainCopy.zoneName}/records`)
+ .reply(200, { records: [DOMAIN_RECORD_0] });
+
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(1);
+ expect(result[0]).to.eql(DOMAIN_RECORD_0.answer);
+ expect(req1.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ nock.cleanAll();
+
+ let DOMAIN_RECORD_0 = {
+ id: 'someid',
+ host: 'test',
+ type: 'A',
+ answer: '1.2.3.4',
+ ttl: 300
+ };
+
+ let req1 = nock(NAMECOM_API)
+ .get(`/domains/${domainCopy.zoneName}/records`)
+ .reply(200, { records: [DOMAIN_RECORD_0] });
+
+ let req2 = nock(NAMECOM_API)
+ .delete(`/domains/${domainCopy.zoneName}/records/${DOMAIN_RECORD_0.id}`)
+ .reply(200, {});
+
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+ });
+
+ describe('namecheap', function () {
+ const NAMECHEAP_ENDPOINT = 'https://api.namecheap.com';
+ const username = 'namecheapuser';
+ const token = 'namecheaptoken';
+
+ // the success answer is always the same
+ const SET_HOSTS_RETURN = `
+
+
+
+ namecheap.domains.dns.sethosts
+
+
+
+
+
+ PHX01APIEXT03
+ --4:00
+ 0.408
+ `;
+
+ before(async function () {
+ domainCopy.provider = 'namecheap';
+ domainCopy.config = {
+ username: username,
+ token: token
+ };
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ beforeEach(function () {
+ nock.cleanAll();
+ });
+
+ it('upsert non-existing record succeeds', function (done) {
+ const GET_HOSTS_RETURN = `
+
+
+
+ namecheap.domains.dns.gethosts
+
+
+
+
+
+
+ PHX01APIEXT04
+ --4:00
+ 0.16
+ `;
+
+ let req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
+ .query({
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.getHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1]
+ })
+ .reply(200, GET_HOSTS_RETURN);
+
+ let req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
+ const expected = {
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.setHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1],
+
+ TTL1: '300',
+ HostName1: '@',
+ RecordType1: 'MX',
+ Address1: 'my.nebulon.space.',
+ EmailType1: 'MX',
+ MXPref1: '10',
+
+ TTL2: '300',
+ HostName2: '@',
+ RecordType2: 'TXT',
+ Address2: 'v=spf1 a:my.nebulon.space ~all',
+
+ TTL3: '300',
+ HostName3: 'test',
+ RecordType3: 'A',
+ Address3: '1.2.3.4',
+ };
+ return _.isEqual(body, expected);
+ })
+ .reply(200, SET_HOSTS_RETURN);
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('upsert multiple non-existing records succeeds', function (done) {
+ const GET_HOSTS_RETURN = `
+
+
+
+ namecheap.domains.dns.gethosts
+
+
+
+
+
+
+ PHX01APIEXT04
+ --4:00
+ 0.16
+ `;
+
+ let req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
+ .query({
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.getHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1]
+ })
+ .reply(200, GET_HOSTS_RETURN);
+
+ let req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
+ const expected = {
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.setHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1],
+
+ TTL1: '300',
+ HostName1: '@',
+ RecordType1: 'MX',
+ Address1: 'my.nebulon.space.',
+ EmailType1: 'MX',
+ MXPref1: '10',
+
+ TTL2: '300',
+ HostName2: '@',
+ RecordType2: 'TXT',
+ Address2: 'v=spf1 a:my.nebulon.space ~all',
+
+ TTL3: '300',
+ HostName3: 'test',
+ RecordType3: 'TXT',
+ Address3: '1.2.3.4',
+
+ TTL4: '300',
+ HostName4: 'test',
+ RecordType4: 'TXT',
+ Address4: '2.3.4.5',
+
+ TTL5: '300',
+ HostName5: 'test',
+ RecordType5: 'TXT',
+ Address5: '3.4.5.6',
+ };
+ return _.isEqual(body, expected);
+ })
+ .reply(200, SET_HOSTS_RETURN);
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'TXT', ['1.2.3.4', '2.3.4.5', '3.4.5.6'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('upsert existing record succeeds', function (done) {
+ const GET_HOSTS_RETURN = `
+
+
+
+ namecheap.domains.dns.gethosts
+
+
+
+
+
+
+ PHX01APIEXT04
+ --4:00
+ 0.16
+ `;
+
+ let req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
+ .query({
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.getHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1]
+ })
+ .reply(200, GET_HOSTS_RETURN);
+
+ let req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
+ const expected = {
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.setHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1],
+
+ TTL1: '300',
+ HostName1: '@',
+ RecordType1: 'MX',
+ Address1: 'my.nebulon.space.',
+ EmailType1: 'MX',
+ MXPref1: '10',
+
+ TTL2: '300',
+ HostName2: 'www',
+ RecordType2: 'CNAME',
+ Address2: '1.2.3.4'
+ };
+ return _.isEqual(body, expected);
+ })
+ .reply(200, SET_HOSTS_RETURN);
+
+ dns.upsertDnsRecords('www', domainCopy.domain, 'CNAME', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('get succeeds', function(done) {
+ const GET_HOSTS_RETURN = `
+
+
+
+ namecheap.domains.dns.gethosts
+
+
+
+
+
+
+
+ PHX01APIEXT04
+ --4:00
+ 0.16
+ `;
+
+ let req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
+ .query({
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.getHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1]
+ })
+ .reply(200, GET_HOSTS_RETURN);
+
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(2);
+ expect(result).to.eql(['1.2.3.4', '2.3.4.5']);
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ const GET_HOSTS_RETURN = `
+
+
+
+ namecheap.domains.dns.gethosts
+
+
+
+
+
+
+ PHX01APIEXT04
+ --4:00
+ 0.16
+ `;
+
+ let req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
+ .query({
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.getHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1]
+ })
+ .reply(200, GET_HOSTS_RETURN);
+
+ let req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
+ const expected = {
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.setHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1],
+
+ TTL1: '300',
+ HostName1: '@',
+ RecordType1: 'MX',
+ Address1: 'my.nebulon.space.',
+ EmailType1: 'MX',
+ MXPref1: '10',
+ };
+ return _.isEqual(body, expected);
+ })
+ .reply(200, SET_HOSTS_RETURN);
+
+ dns.removeDnsRecords('www', domainCopy.domain, 'CNAME', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+ expect(req2.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ it('del succeeds with non-existing domain', function (done) {
+ const GET_HOSTS_RETURN = `
+
+
+
+ namecheap.domains.dns.gethosts
+
+
+
+
+
+
+ PHX01APIEXT04
+ --4:00
+ 0.16
+ `;
+
+ let req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
+ .query({
+ ApiUser: username,
+ ApiKey: token,
+ UserName: username,
+ ClientIp: '127.0.0.1',
+ Command: 'namecheap.domains.dns.getHosts',
+ SLD: domainCopy.zoneName.split('.')[0],
+ TLD: domainCopy.zoneName.split('.')[1]
+ })
+ .reply(200, GET_HOSTS_RETURN);
+
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(req1.isDone()).to.be.ok();
+
+ done();
+ });
+ });
+
+ });
+
+ describe('route53', function () {
+ // do not clear this with [] but .length = 0 so we don't loose the reference in mockery
+ let awsAnswerQueue = [];
+
+ let AWS_HOSTED_ZONES = null;
+
+ before(async function () {
+ domainCopy.provider = 'route53';
+ domainCopy.config = {
+ accessKeyId: 'unused',
+ secretAccessKey: 'unused'
+ };
+
+ AWS_HOSTED_ZONES = {
+ HostedZones: [{
+ Id: '/hostedzone/Z34G16B38TNZ9L',
+ Name: domainCopy.zoneName + '.',
+ CallerReference: '305AFD59-9D73-4502-B020-F4E6F889CB30',
+ ResourceRecordSetCount: 2,
+ ChangeInfo: {
+ Id: '/change/CKRTFJA0ANHXB',
+ Status: 'INSYNC'
+ }
+ }, {
+ Id: '/hostedzone/Z3OFC3B6E8YTA7',
+ Name: 'cloudron.us.',
+ CallerReference: '0B37F2DE-21A4-E678-BA32-3FC8AF0CF635',
+ Config: {},
+ ResourceRecordSetCount: 2,
+ ChangeInfo: {
+ Id: '/change/C2682N5HXP0BZ5',
+ Status: 'INSYNC'
+ }
+ }],
+ IsTruncated: false,
+ MaxItems: '100'
+ };
+
+ function mockery(queue) {
+ return function (options, callback) {
+ expect(options).to.be.an(Object);
+
+ let elem = queue.shift();
+ if (!Array.isArray(elem)) throw (new Error('Mock answer required'));
+
+ // if no callback passed, return a req object with send();
+ if (typeof callback !== 'function') {
+ return {
+ httpRequest: { headers: {} },
+ send: function (callback) {
+ expect(callback).to.be.a(Function);
+ callback(elem[0], elem[1]);
+ }
+ };
+ } else {
+ callback(elem[0], elem[1]);
+ }
+ };
+ }
+
+ function Route53Mock(cfg) {
+ expect(cfg).to.eql({
+ accessKeyId: domainCopy.config.accessKeyId,
+ secretAccessKey: domainCopy.config.secretAccessKey,
+ region: 'us-east-1'
+ });
+ }
+ Route53Mock.prototype.getHostedZone = mockery(awsAnswerQueue);
+ Route53Mock.prototype.getChange = mockery(awsAnswerQueue);
+ Route53Mock.prototype.changeResourceRecordSets = mockery(awsAnswerQueue);
+ Route53Mock.prototype.listResourceRecordSets = mockery(awsAnswerQueue);
+ Route53Mock.prototype.listHostedZonesByName = mockery(awsAnswerQueue);
+
+ // override route53 in AWS
+ // Comment this out and replace the config with real tokens to test against AWS proper
+ AWS._originalRoute53 = AWS.Route53;
+ AWS.Route53 = Route53Mock;
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ after(function () {
+ AWS.Route53 = AWS._originalRoute53;
+ delete AWS._originalRoute53;
+ });
+
+ it('upsert non-existing record succeeds', function (done) {
+ awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
+ awsAnswerQueue.push([null, {
+ ChangeInfo: {
+ Id: '/change/C2QLKQIWEI0BZF',
+ Status: 'PENDING',
+ SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
+ }
+ }]);
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(awsAnswerQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('upsert existing record succeeds', function (done) {
+ awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
+ awsAnswerQueue.push([null, {
+ ChangeInfo: {
+ Id: '/change/C2QLKQIWEI0BZF',
+ Status: 'PENDING',
+ SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
+ }
+ }]);
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(awsAnswerQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('upsert multiple record succeeds', function (done) {
+ awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
+ awsAnswerQueue.push([null, {
+ ChangeInfo: {
+ Id: '/change/C2QLKQIWEI0BZF',
+ Status: 'PENDING',
+ SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
+ }
+ }]);
+
+ dns.upsertDnsRecords('', domainCopy.domain, 'TXT', ['first', 'second', 'third'], function (error) {
+ expect(error).to.eql(null);
+ expect(awsAnswerQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('get succeeds', function (done) {
+ awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
+ awsAnswerQueue.push([null, {
+ ResourceRecordSets: [{
+ Name: 'test.' + domainCopy.zoneName + '.',
+ Type: 'A',
+ ResourceRecords: [{
+ Value: '1.2.3.4'
+ }]
+ }]
+ }]);
+
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(1);
+ expect(result[0]).to.eql('1.2.3.4');
+ expect(awsAnswerQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
+ awsAnswerQueue.push([null, {
+ ChangeInfo: {
+ Id: '/change/C2QLKQIWEI0BZF',
+ Status: 'PENDING',
+ SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
+ }
+ }]);
+
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(awsAnswerQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+ });
+
+ describe('gcdns', function () {
+ let HOSTED_ZONES = [];
+ let zoneQueue = [];
+ let _OriginalGCDNS;
+
+ before(async function () {
+ domainCopy.provider = 'gcdns';
+ domainCopy.config = {
+ projectId: 'my-dns-proj',
+ credentials: {
+ 'client_email': '123456789349-compute@developer.gserviceaccount.com',
+ 'private_key': 'privatehushhush'
+ }
+ };
+
+ function mockery(queue) {
+ return function () {
+ let callback = arguments[--arguments.length];
+
+ let elem = queue.shift();
+ if (!Array.isArray(elem)) throw (new Error('Mock answer required'));
+
+ // if no callback passed, return a req object with send();
+ if (typeof callback !== 'function') {
+ return {
+ httpRequest: { headers: {} },
+ send: function (callback) {
+ expect(callback).to.be.a(Function);
+ callback.apply(callback, elem);
+ }
+ };
+ } else {
+ callback.apply(callback, elem);
+ }
+ };
+ }
+
+ function fakeZone(name, ns, recordQueue) {
+ let zone = new GCDNS().zone(name.replace('.', '-'));
+ zone.metadata.dnsName = name + '.';
+ zone.metadata.nameServers = ns || ['8.8.8.8', '8.8.4.4'];
+ zone.getRecords = mockery(recordQueue || zoneQueue);
+ zone.createChange = mockery(recordQueue || zoneQueue);
+ zone.replaceRecords = mockery(recordQueue || zoneQueue);
+ zone.deleteRecords = mockery(recordQueue || zoneQueue);
+ return zone;
+ }
+ HOSTED_ZONES = [fakeZone(domainCopy.domain), fakeZone('cloudron.us')];
+
+ _OriginalGCDNS = GCDNS.prototype.getZones;
+ GCDNS.prototype.getZones = mockery(zoneQueue);
+
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
+ });
+
+ after(function () {
+ GCDNS.prototype.getZones = _OriginalGCDNS;
+ _OriginalGCDNS = null;
+ });
+
+ it('upsert non-existing record succeeds', function (done) {
+ zoneQueue.push([null, HOSTED_ZONES]); // getZone
+ zoneQueue.push([null, []]); // getRecords
+ zoneQueue.push([null, { id: '1' }]);
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(zoneQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('upsert existing record succeeds', function (done) {
+ zoneQueue.push([null, HOSTED_ZONES]);
+ zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
+ zoneQueue.push([null, { id: '2' }]);
+
+ dns.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(zoneQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('upsert multiple record succeeds', function (done) {
+ zoneQueue.push([null, HOSTED_ZONES]);
+ zoneQueue.push([null, []]); // getRecords
+ zoneQueue.push([null, { id: '3' }]);
+
+ dns.upsertDnsRecords('', domainCopy.domain, 'TXT', ['first', 'second', 'third'], function (error) {
+ expect(error).to.eql(null);
+ expect(zoneQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('get succeeds', function (done) {
+ zoneQueue.push([null, HOSTED_ZONES]);
+ zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['1.2.3.4', '5.6.7.8'], ttl: 1 })]]);
+
+ dns.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
+ expect(error).to.eql(null);
+ expect(result).to.be.an(Array);
+ expect(result.length).to.eql(2);
+ expect(result).to.eql(['1.2.3.4', '5.6.7.8']);
+ expect(zoneQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+
+ it('del succeeds', function (done) {
+ zoneQueue.push([null, HOSTED_ZONES]);
+ zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
+ zoneQueue.push([null, { id: '5' }]);
+
+ dns.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
+ expect(error).to.eql(null);
+ expect(zoneQueue.length).to.eql(0);
+
+ done();
+ });
+ });
+ });
+});
diff --git a/src/test/dns-test.js b/src/test/dns-test.js
index 4ef65d8d7..abae0c4db 100644
--- a/src/test/dns-test.js
+++ b/src/test/dns-test.js
@@ -1,1266 +1,108 @@
-/* jslint node:true */
/* global it:false */
/* global describe:false */
/* global before:false */
-/* global beforeEach:false */
/* global after:false */
'use strict';
-const AWS = require('aws-sdk'),
- common = require('./common.js'),
- domains = require('../domains.js'),
- expect = require('expect.js'),
- GCDNS = require('@google-cloud/dns').DNS,
- nock = require('nock'),
- _ = require('underscore');
+const common = require('./common.js'),
+ dns = require('../dns.js'),
+ expect = require('expect.js');
-describe('dns provider', function () {
- const { setup, cleanup, auditSource, domain } = common;
- const domainCopy = Object.assign({}, domain); // make a copy
+describe('DNS', function () {
+ const { setup, cleanup, app, domain } = common;
before(setup);
after(cleanup);
- describe('noop', function () {
- before(function (done) {
- domainCopy.provider = 'noop';
- domainCopy.config = {};
-
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
+ describe('validateHostname', function () {
+ it('does not allow admin subdomain', function () {
+ expect(dns.validateHostname('my', domain)).to.be.an(Error);
});
- it('upsert succeeds', function (done) {
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
-
- done();
- });
+ it('cannot have >63 length subdomains', function () {
+ var s = Array(64).fill('s').join('');
+ expect(dns.validateHostname(s, domain)).to.be.an(Error);
+ domain.zoneName = `dev.${s}.example.com`;
+ expect(dns.validateHostname(`dev.${s}`, domain)).to.be.an(Error);
});
- it('get succeeds', function (done) {
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(0);
-
- done();
- });
+ it('allows only alphanumerics and hypen', function () {
+ expect(dns.validateHostname('#2r', domain)).to.be.an(Error);
+ expect(dns.validateHostname('a%b', domain)).to.be.an(Error);
+ expect(dns.validateHostname('ab_', domain)).to.be.an(Error);
+ expect(dns.validateHostname('ab.', domain)).to.be.an(Error);
+ expect(dns.validateHostname('ab..c', domain)).to.be.an(Error);
+ expect(dns.validateHostname('.ab', domain)).to.be.an(Error);
+ expect(dns.validateHostname('-ab', domain)).to.be.an(Error);
+ expect(dns.validateHostname('ab-', domain)).to.be.an(Error);
});
- it('del succeeds', function (done) {
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
+ it('total length cannot exceed 255', function () {
+ var s = '';
+ for (var i = 0; i < (255 - 'example.com'.length); i++) s += 's';
- done();
- });
+ expect(dns.validateHostname(s, domain)).to.be.an(Error);
+ });
+
+ it('allow valid domains', function () {
+ expect(dns.validateHostname('a', domain)).to.be(null);
+ expect(dns.validateHostname('a0-x', domain)).to.be(null);
+ expect(dns.validateHostname('a0.x', domain)).to.be(null);
+ expect(dns.validateHostname('a0.x.y', domain)).to.be(null);
+ expect(dns.validateHostname('01', domain)).to.be(null);
});
});
- describe('digitalocean', function () {
- var TOKEN = 'sometoken';
- var DIGITALOCEAN_ENDPOINT = 'https://api.digitalocean.com';
-
- before(function (done) {
- domainCopy.provider = 'digitalocean';
- domainCopy.config = {
- token: TOKEN
+ describe('getName', function () {
+ it('works with zoneName==domain', function () {
+ const d = {
+ domain: 'example.com',
+ zoneName: 'example.com',
+ config: {}
};
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
+ expect(dns.getName(d, '', 'A')).to.be('');
+ expect(dns.getName(d, 'www', 'A')).to.be('www');
+ expect(dns.getName(d, 'www.dev', 'A')).to.be('www.dev');
+
+ expect(dns.getName(d, '', 'MX')).to.be('');
+
+ expect(dns.getName(d, '', 'TXT')).to.be('');
+ expect(dns.getName(d, 'www', 'TXT')).to.be('www');
+ expect(dns.getName(d, 'www.dev', 'TXT')).to.be('www.dev');
});
- it('upsert non-existing record succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- id: 3352892,
- type: 'A',
- name: '@',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
+ it('works when zoneName!=domain', function () {
+ const d = {
+ domain: 'dev.example.com',
+ zoneName: 'example.com',
+ config: {}
};
- var req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .get('/v2/domains/' + domainCopy.zoneName + '/records')
- .reply(200, { domain_records: [] });
- var req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .post('/v2/domains/' + domainCopy.zoneName + '/records')
- .reply(201, { domain_record: DOMAIN_RECORD_0 });
+ expect(dns.getName(d, '', 'A')).to.be('dev');
+ expect(dns.getName(d, 'www', 'A')).to.be('www.dev');
+ expect(dns.getName(d, 'www.dev', 'A')).to.be('www.dev.dev');
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
+ expect(dns.getName(d, '', 'MX')).to.be('dev');
- done();
- });
- });
-
- it('upsert existing record succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- id: 3352892,
- type: 'A',
- name: '@',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_1 = {
- id: 3352893,
- type: 'A',
- name: 'test',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_1_NEW = {
- id: 3352893,
- type: 'A',
- name: 'test',
- data: '1.2.3.5',
- priority: null,
- port: null,
- weight: null
- };
-
- var req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .get('/v2/domains/' + domainCopy.zoneName + '/records')
- .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1] });
- var req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .put('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_1.id)
- .reply(200, { domain_record: DOMAIN_RECORD_1_NEW });
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', [DOMAIN_RECORD_1_NEW.data], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('upsert multiple record succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- id: 3352892,
- type: 'A',
- name: '@',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_1 = {
- id: 3352893,
- type: 'TXT',
- name: '@',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_1_NEW = {
- id: 3352893,
- type: 'TXT',
- name: '@',
- data: 'somethingnew',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_2 = {
- id: 3352894,
- type: 'TXT',
- name: '@',
- data: 'something',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_2_NEW = {
- id: 3352894,
- type: 'TXT',
- name: '@',
- data: 'somethingnew',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_3_NEW = {
- id: 3352895,
- type: 'TXT',
- name: '@',
- data: 'thirdnewone',
- priority: null,
- port: null,
- weight: null
- };
-
- var req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .get('/v2/domains/' + domainCopy.zoneName + '/records')
- .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1, DOMAIN_RECORD_2] });
- var req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .put('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_1.id)
- .reply(200, { domain_record: DOMAIN_RECORD_1_NEW });
- var req3 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .put('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_2.id)
- .reply(200, { domain_record: DOMAIN_RECORD_2_NEW });
- var req4 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .post('/v2/domains/' + domainCopy.zoneName + '/records')
- .reply(201, { domain_record: DOMAIN_RECORD_2_NEW });
-
- domains.upsertDnsRecords('', domainCopy.domain, 'TXT', [DOMAIN_RECORD_2_NEW.data, DOMAIN_RECORD_1_NEW.data, DOMAIN_RECORD_3_NEW.data], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
- expect(req3.isDone()).to.be.ok();
- expect(req4.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('get succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- id: 3352892,
- type: 'A',
- name: '@',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_1 = {
- id: 3352893,
- type: 'A',
- name: 'test',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .get('/v2/domains/' + domainCopy.zoneName + '/records')
- .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1] });
-
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(1);
- expect(result[0]).to.eql(DOMAIN_RECORD_1.data);
- expect(req1.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('del succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- id: 3352892,
- type: 'A',
- name: '@',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var DOMAIN_RECORD_1 = {
- id: 3352893,
- type: 'A',
- name: 'test',
- data: '1.2.3.4',
- priority: null,
- port: null,
- weight: null
- };
-
- var req1 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .get('/v2/domains/' + domainCopy.zoneName + '/records')
- .reply(200, { domain_records: [DOMAIN_RECORD_0, DOMAIN_RECORD_1] });
- var req2 = nock(DIGITALOCEAN_ENDPOINT).filteringRequestBody(function () { return false; })
- .delete('/v2/domains/' + domainCopy.zoneName + '/records/' + DOMAIN_RECORD_1.id)
- .reply(204, {});
-
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
+ expect(dns.getName(d, '', 'TXT')).to.be('dev');
+ expect(dns.getName(d, 'www', 'TXT')).to.be('www.dev');
+ expect(dns.getName(d, 'www.dev', 'TXT')).to.be('www.dev.dev');
});
});
- describe('godaddy', function () {
- var KEY = 'somekey', SECRET = 'somesecret';
- var GODADDY_API = 'https://api.godaddy.com/v1/domains';
-
- before(function (done) {
- domainCopy.provider = 'godaddy';
- domainCopy.config = {
- apiKey: KEY,
- apiSecret: SECRET
- };
-
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
- });
-
- it('upsert record succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = [{
- ttl: 600,
- data: '1.2.3.4'
- }];
-
- var req1 = nock(GODADDY_API)
- .put('/' + domainCopy.zoneName + '/records/A/test', DOMAIN_RECORD_0)
- .reply(200, {});
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
-
+ describe('register', function () {
+ it('registers subdomain', function (done) {
+ dns.registerLocations([ { subdomain: app.location, domain: app.domain } ], { overwriteDns: true }, (/*progress*/) => {}, function (error) {
+ expect(error).to.be(null);
done();
});
});
- it('get succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = [{
- ttl: 600,
- data: '1.2.3.4'
- }];
-
- var req1 = nock(GODADDY_API)
- .get('/' + domainCopy.zoneName + '/records/A/test')
- .reply(200, DOMAIN_RECORD_0);
-
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(1);
- expect(result[0]).to.eql(DOMAIN_RECORD_0[0].data);
- expect(req1.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('del succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = [{ // existing
- ttl: 600,
- data: '1.2.3.4'
- }];
-
- var DOMAIN_RECORD_1 = [{ // replaced
- ttl: 600,
- data: '0.0.0.0'
- }];
-
- var req1 = nock(GODADDY_API)
- .get('/' + domainCopy.zoneName + '/records/A/test')
- .reply(200, DOMAIN_RECORD_0);
-
- var req2 = nock(GODADDY_API)
- .put('/' + domainCopy.zoneName + '/records/A/test', DOMAIN_RECORD_1)
- .reply(200, {});
-
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
- });
-
- describe('gandi', function () {
- var TOKEN = 'sometoken';
- var GANDI_API = 'https://dns.api.gandi.net/api/v5';
-
- before(function (done) {
- domainCopy.provider = 'gandi';
- domainCopy.config = {
- token: TOKEN
- };
-
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
- });
-
- it('upsert record succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- 'rrset_ttl': 300,
- 'rrset_values': ['1.2.3.4']
- };
-
- var req1 = nock(GANDI_API)
- .put('/domains/' + domainCopy.zoneName + '/records/test/A', DOMAIN_RECORD_0)
- .reply(201, { message: 'Zone Record Created' });
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('get succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- 'rrset_type': 'A',
- 'rrset_ttl': 600,
- 'rrset_name': 'test',
- 'rrset_values': ['1.2.3.4']
- };
-
- var req1 = nock(GANDI_API)
- .get('/domains/' + domainCopy.zoneName + '/records/test/A')
- .reply(200, DOMAIN_RECORD_0);
-
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(1);
- expect(result[0]).to.eql(DOMAIN_RECORD_0.rrset_values[0]);
- expect(req1.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('del succeeds', function (done) {
- nock.cleanAll();
-
- var req2 = nock(GANDI_API)
- .delete('/domains/' + domainCopy.zoneName + '/records/test/A')
- .reply(204, {});
-
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
- });
-
- describe('name.com', function () {
- const TOKEN = 'sometoken';
- const NAMECOM_API = 'https://api.name.com/v4';
-
- before(function (done) {
- domainCopy.provider = 'namecom';
- domainCopy.config = {
- username: 'fake',
- token: TOKEN
- };
-
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
- });
-
- it('upsert record succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- host: 'test',
- type: 'A',
- answer: '1.2.3.4',
- ttl: 300
- };
-
- var req1 = nock(NAMECOM_API)
- .get(`/domains/${domainCopy.zoneName}/records`)
- .reply(200, { records: [] });
-
- var req2 = nock(NAMECOM_API)
- .post(`/domains/${domainCopy.zoneName}/records`, DOMAIN_RECORD_0)
- .reply(200, {});
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('get succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- host: 'test',
- type: 'A',
- answer: '1.2.3.4',
- ttl: 300
- };
-
- var req1 = nock(NAMECOM_API)
- .get(`/domains/${domainCopy.zoneName}/records`)
- .reply(200, { records: [DOMAIN_RECORD_0] });
-
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(1);
- expect(result[0]).to.eql(DOMAIN_RECORD_0.answer);
- expect(req1.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('del succeeds', function (done) {
- nock.cleanAll();
-
- var DOMAIN_RECORD_0 = {
- id: 'someid',
- host: 'test',
- type: 'A',
- answer: '1.2.3.4',
- ttl: 300
- };
-
- var req1 = nock(NAMECOM_API)
- .get(`/domains/${domainCopy.zoneName}/records`)
- .reply(200, { records: [DOMAIN_RECORD_0] });
-
- var req2 = nock(NAMECOM_API)
- .delete(`/domains/${domainCopy.zoneName}/records/${DOMAIN_RECORD_0.id}`)
- .reply(200, {});
-
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
- });
-
- describe('namecheap', function () {
- const NAMECHEAP_ENDPOINT = 'https://api.namecheap.com';
- const username = 'namecheapuser';
- const token = 'namecheaptoken';
-
- // the success answer is always the same
- const SET_HOSTS_RETURN = `
-
-
-
- namecheap.domains.dns.sethosts
-
-
-
-
-
- PHX01APIEXT03
- --4:00
- 0.408
- `;
-
- before(function (done) {
- domainCopy.provider = 'namecheap';
- domainCopy.config = {
- username: username,
- token: token
- };
-
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
- });
-
- beforeEach(function () {
- nock.cleanAll();
- });
-
- it('upsert non-existing record succeeds', function (done) {
- const GET_HOSTS_RETURN = `
-
-
-
- namecheap.domains.dns.gethosts
-
-
-
-
-
-
- PHX01APIEXT04
- --4:00
- 0.16
- `;
-
- var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
- .query({
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.getHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1]
- })
- .reply(200, GET_HOSTS_RETURN);
-
- var req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
- const expected = {
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.setHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1],
-
- TTL1: '300',
- HostName1: '@',
- RecordType1: 'MX',
- Address1: 'my.nebulon.space.',
- EmailType1: 'MX',
- MXPref1: '10',
-
- TTL2: '300',
- HostName2: '@',
- RecordType2: 'TXT',
- Address2: 'v=spf1 a:my.nebulon.space ~all',
-
- TTL3: '300',
- HostName3: 'test',
- RecordType3: 'A',
- Address3: '1.2.3.4',
- };
- return _.isEqual(body, expected);
- })
- .reply(200, SET_HOSTS_RETURN);
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('upsert multiple non-existing records succeeds', function (done) {
- const GET_HOSTS_RETURN = `
-
-
-
- namecheap.domains.dns.gethosts
-
-
-
-
-
-
- PHX01APIEXT04
- --4:00
- 0.16
- `;
-
- var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
- .query({
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.getHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1]
- })
- .reply(200, GET_HOSTS_RETURN);
-
- var req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
- const expected = {
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.setHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1],
-
- TTL1: '300',
- HostName1: '@',
- RecordType1: 'MX',
- Address1: 'my.nebulon.space.',
- EmailType1: 'MX',
- MXPref1: '10',
-
- TTL2: '300',
- HostName2: '@',
- RecordType2: 'TXT',
- Address2: 'v=spf1 a:my.nebulon.space ~all',
-
- TTL3: '300',
- HostName3: 'test',
- RecordType3: 'TXT',
- Address3: '1.2.3.4',
-
- TTL4: '300',
- HostName4: 'test',
- RecordType4: 'TXT',
- Address4: '2.3.4.5',
-
- TTL5: '300',
- HostName5: 'test',
- RecordType5: 'TXT',
- Address5: '3.4.5.6',
- };
- return _.isEqual(body, expected);
- })
- .reply(200, SET_HOSTS_RETURN);
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'TXT', ['1.2.3.4', '2.3.4.5', '3.4.5.6'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('upsert existing record succeeds', function (done) {
- const GET_HOSTS_RETURN = `
-
-
-
- namecheap.domains.dns.gethosts
-
-
-
-
-
-
- PHX01APIEXT04
- --4:00
- 0.16
- `;
-
- var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
- .query({
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.getHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1]
- })
- .reply(200, GET_HOSTS_RETURN);
-
- var req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
- const expected = {
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.setHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1],
-
- TTL1: '300',
- HostName1: '@',
- RecordType1: 'MX',
- Address1: 'my.nebulon.space.',
- EmailType1: 'MX',
- MXPref1: '10',
-
- TTL2: '300',
- HostName2: 'www',
- RecordType2: 'CNAME',
- Address2: '1.2.3.4'
- };
- return _.isEqual(body, expected);
- })
- .reply(200, SET_HOSTS_RETURN);
-
- domains.upsertDnsRecords('www', domainCopy.domain, 'CNAME', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('get succeeds', function(done) {
- const GET_HOSTS_RETURN = `
-
-
-
- namecheap.domains.dns.gethosts
-
-
-
-
-
-
-
- PHX01APIEXT04
- --4:00
- 0.16
- `;
-
- var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
- .query({
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.getHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1]
- })
- .reply(200, GET_HOSTS_RETURN);
-
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(2);
- expect(result).to.eql(['1.2.3.4', '2.3.4.5']);
-
- done();
- });
- });
-
- it('del succeeds', function (done) {
- const GET_HOSTS_RETURN = `
-
-
-
- namecheap.domains.dns.gethosts
-
-
-
-
-
-
- PHX01APIEXT04
- --4:00
- 0.16
- `;
-
- var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
- .query({
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.getHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1]
- })
- .reply(200, GET_HOSTS_RETURN);
-
- var req2 = nock(NAMECHEAP_ENDPOINT).post('/xml.response', (body) => {
- const expected = {
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.setHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1],
-
- TTL1: '300',
- HostName1: '@',
- RecordType1: 'MX',
- Address1: 'my.nebulon.space.',
- EmailType1: 'MX',
- MXPref1: '10',
- };
- return _.isEqual(body, expected);
- })
- .reply(200, SET_HOSTS_RETURN);
-
- domains.removeDnsRecords('www', domainCopy.domain, 'CNAME', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
- expect(req2.isDone()).to.be.ok();
-
- done();
- });
- });
-
- it('del succeeds with non-existing domain', function (done) {
- const GET_HOSTS_RETURN = `
-
-
-
- namecheap.domains.dns.gethosts
-
-
-
-
-
-
- PHX01APIEXT04
- --4:00
- 0.16
- `;
-
- var req1 = nock(NAMECHEAP_ENDPOINT).get('/xml.response')
- .query({
- ApiUser: username,
- ApiKey: token,
- UserName: username,
- ClientIp: '127.0.0.1',
- Command: 'namecheap.domains.dns.getHosts',
- SLD: domainCopy.zoneName.split('.')[0],
- TLD: domainCopy.zoneName.split('.')[1]
- })
- .reply(200, GET_HOSTS_RETURN);
-
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(req1.isDone()).to.be.ok();
-
- done();
- });
- });
-
- });
-
- describe('route53', function () {
- // do not clear this with [] but .length = 0 so we don't loose the reference in mockery
- var awsAnswerQueue = [];
-
- var AWS_HOSTED_ZONES = null;
-
- before(function (done) {
- domainCopy.provider = 'route53';
- domainCopy.config = {
- accessKeyId: 'unused',
- secretAccessKey: 'unused'
- };
-
- AWS_HOSTED_ZONES = {
- HostedZones: [{
- Id: '/hostedzone/Z34G16B38TNZ9L',
- Name: domainCopy.zoneName + '.',
- CallerReference: '305AFD59-9D73-4502-B020-F4E6F889CB30',
- ResourceRecordSetCount: 2,
- ChangeInfo: {
- Id: '/change/CKRTFJA0ANHXB',
- Status: 'INSYNC'
- }
- }, {
- Id: '/hostedzone/Z3OFC3B6E8YTA7',
- Name: 'cloudron.us.',
- CallerReference: '0B37F2DE-21A4-E678-BA32-3FC8AF0CF635',
- Config: {},
- ResourceRecordSetCount: 2,
- ChangeInfo: {
- Id: '/change/C2682N5HXP0BZ5',
- Status: 'INSYNC'
- }
- }],
- IsTruncated: false,
- MaxItems: '100'
- };
-
- function mockery(queue) {
- return function (options, callback) {
- expect(options).to.be.an(Object);
-
- var elem = queue.shift();
- if (!Array.isArray(elem)) throw (new Error('Mock answer required'));
-
- // if no callback passed, return a req object with send();
- if (typeof callback !== 'function') {
- return {
- httpRequest: { headers: {} },
- send: function (callback) {
- expect(callback).to.be.a(Function);
- callback(elem[0], elem[1]);
- }
- };
- } else {
- callback(elem[0], elem[1]);
- }
- };
- }
-
- function Route53Mock(cfg) {
- expect(cfg).to.eql({
- accessKeyId: domainCopy.config.accessKeyId,
- secretAccessKey: domainCopy.config.secretAccessKey,
- region: 'us-east-1'
- });
- }
- Route53Mock.prototype.getHostedZone = mockery(awsAnswerQueue);
- Route53Mock.prototype.getChange = mockery(awsAnswerQueue);
- Route53Mock.prototype.changeResourceRecordSets = mockery(awsAnswerQueue);
- Route53Mock.prototype.listResourceRecordSets = mockery(awsAnswerQueue);
- Route53Mock.prototype.listHostedZonesByName = mockery(awsAnswerQueue);
-
- // override route53 in AWS
- // Comment this out and replace the config with real tokens to test against AWS proper
- AWS._originalRoute53 = AWS.Route53;
- AWS.Route53 = Route53Mock;
-
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
- });
-
- after(function () {
- AWS.Route53 = AWS._originalRoute53;
- delete AWS._originalRoute53;
- });
-
- it('upsert non-existing record succeeds', function (done) {
- awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
- awsAnswerQueue.push([null, {
- ChangeInfo: {
- Id: '/change/C2QLKQIWEI0BZF',
- Status: 'PENDING',
- SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
- }
- }]);
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(awsAnswerQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('upsert existing record succeeds', function (done) {
- awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
- awsAnswerQueue.push([null, {
- ChangeInfo: {
- Id: '/change/C2QLKQIWEI0BZF',
- Status: 'PENDING',
- SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
- }
- }]);
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(awsAnswerQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('upsert multiple record succeeds', function (done) {
- awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
- awsAnswerQueue.push([null, {
- ChangeInfo: {
- Id: '/change/C2QLKQIWEI0BZF',
- Status: 'PENDING',
- SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
- }
- }]);
-
- domains.upsertDnsRecords('', domainCopy.domain, 'TXT', ['first', 'second', 'third'], function (error) {
- expect(error).to.eql(null);
- expect(awsAnswerQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('get succeeds', function (done) {
- awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
- awsAnswerQueue.push([null, {
- ResourceRecordSets: [{
- Name: 'test.' + domainCopy.zoneName + '.',
- Type: 'A',
- ResourceRecords: [{
- Value: '1.2.3.4'
- }]
- }]
- }]);
-
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(1);
- expect(result[0]).to.eql('1.2.3.4');
- expect(awsAnswerQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('del succeeds', function (done) {
- awsAnswerQueue.push([null, AWS_HOSTED_ZONES]);
- awsAnswerQueue.push([null, {
- ChangeInfo: {
- Id: '/change/C2QLKQIWEI0BZF',
- Status: 'PENDING',
- SubmittedAt: 'Mon Aug 04 2014 17: 44: 49 GMT - 0700(PDT)'
- }
- }]);
-
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(awsAnswerQueue.length).to.eql(0);
-
- done();
- });
- });
- });
-
- describe('gcdns', function () {
- var HOSTED_ZONES = [];
- var zoneQueue = [];
- var _OriginalGCDNS;
-
- before(function (done) {
- domainCopy.provider = 'gcdns';
- domainCopy.config = {
- projectId: 'my-dns-proj',
- credentials: {
- 'client_email': '123456789349-compute@developer.gserviceaccount.com',
- 'private_key': 'privatehushhush'
- }
- };
-
- function mockery(queue) {
- return function () {
- var callback = arguments[--arguments.length];
-
- var elem = queue.shift();
- if (!Array.isArray(elem)) throw (new Error('Mock answer required'));
-
- // if no callback passed, return a req object with send();
- if (typeof callback !== 'function') {
- return {
- httpRequest: { headers: {} },
- send: function (callback) {
- expect(callback).to.be.a(Function);
- callback.apply(callback, elem);
- }
- };
- } else {
- callback.apply(callback, elem);
- }
- };
- }
-
- function fakeZone(name, ns, recordQueue) {
- var zone = new GCDNS().zone(name.replace('.', '-'));
- zone.metadata.dnsName = name + '.';
- zone.metadata.nameServers = ns || ['8.8.8.8', '8.8.4.4'];
- zone.getRecords = mockery(recordQueue || zoneQueue);
- zone.createChange = mockery(recordQueue || zoneQueue);
- zone.replaceRecords = mockery(recordQueue || zoneQueue);
- zone.deleteRecords = mockery(recordQueue || zoneQueue);
- return zone;
- }
- HOSTED_ZONES = [fakeZone(domainCopy.domain), fakeZone('cloudron.us')];
-
- _OriginalGCDNS = GCDNS.prototype.getZones;
- GCDNS.prototype.getZones = mockery(zoneQueue);
-
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
- });
-
- after(function () {
- GCDNS.prototype.getZones = _OriginalGCDNS;
- _OriginalGCDNS = null;
- });
-
- it('upsert non-existing record succeeds', function (done) {
- zoneQueue.push([null, HOSTED_ZONES]); // getZone
- zoneQueue.push([null, []]); // getRecords
- zoneQueue.push([null, { id: '1' }]);
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(zoneQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('upsert existing record succeeds', function (done) {
- zoneQueue.push([null, HOSTED_ZONES]);
- zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
- zoneQueue.push([null, { id: '2' }]);
-
- domains.upsertDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(zoneQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('upsert multiple record succeeds', function (done) {
- zoneQueue.push([null, HOSTED_ZONES]);
- zoneQueue.push([null, []]); // getRecords
- zoneQueue.push([null, { id: '3' }]);
-
- domains.upsertDnsRecords('', domainCopy.domain, 'TXT', ['first', 'second', 'third'], function (error) {
- expect(error).to.eql(null);
- expect(zoneQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('get succeeds', function (done) {
- zoneQueue.push([null, HOSTED_ZONES]);
- zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['1.2.3.4', '5.6.7.8'], ttl: 1 })]]);
-
- domains.getDnsRecords('test', domainCopy.domain, 'A', function (error, result) {
- expect(error).to.eql(null);
- expect(result).to.be.an(Array);
- expect(result.length).to.eql(2);
- expect(result).to.eql(['1.2.3.4', '5.6.7.8']);
- expect(zoneQueue.length).to.eql(0);
-
- done();
- });
- });
-
- it('del succeeds', function (done) {
- zoneQueue.push([null, HOSTED_ZONES]);
- zoneQueue.push([null, [new GCDNS().zone('test').record('A', { 'name': 'test', data: ['5.6.7.8'], ttl: 1 })]]);
- zoneQueue.push([null, { id: '5' }]);
-
- domains.removeDnsRecords('test', domainCopy.domain, 'A', ['1.2.3.4'], function (error) {
- expect(error).to.eql(null);
- expect(zoneQueue.length).to.eql(0);
-
+ it('unregisters subdomain', function (done) {
+ dns.unregisterLocations([ { subdomain: app.location, domain: app.domain } ], (/*progress*/) => {}, function (error) {
+ expect(error).to.be(null);
done();
});
});
diff --git a/src/test/domains-test.js b/src/test/domains-test.js
index e266ec0d6..3e75af166 100644
--- a/src/test/domains-test.js
+++ b/src/test/domains-test.js
@@ -1,3 +1,4 @@
+/* jslint node:true */
/* global it:false */
/* global describe:false */
/* global before:false */
@@ -5,146 +6,118 @@
'use strict';
-const common = require('./common.js'),
+const appdb = require('../appdb.js'),
+ BoxError = require('../boxerror.js'),
+ common = require('./common.js'),
domains = require('../domains.js'),
expect = require('expect.js'),
- js2xml = require('js2xmlparser').parse,
- nock = require('nock');
+ safe = require('safetydance'),
+ util = require('util');
describe('Domains', function () {
- const { setup, cleanup, app, domain } = common;
+ const { setup, cleanup, domain, app, auditSource } = common;
before(setup);
after(cleanup);
- describe('validateHostname', function () {
- it('does not allow admin subdomain', function () {
- expect(domains.validateHostname('my', domain)).to.be.an(Error);
- });
+ const DOMAIN_0 = {
+ domain: 'z0.com',
+ zoneName: 'z0.com',
+ provider: 'noop',
+ config: { },
+ fallbackCertificate: null,
+ tlsConfig: {
+ provider: 'fallback'
+ },
+ wellKnown: null
+ };
- it('cannot have >63 length subdomains', function () {
- var s = Array(64).fill('s').join('');
- expect(domains.validateHostname(s, domain)).to.be.an(Error);
- domain.zoneName = `dev.${s}.example.com`;
- expect(domains.validateHostname(`dev.${s}`, domain)).to.be.an(Error);
- });
-
- it('allows only alphanumerics and hypen', function () {
- expect(domains.validateHostname('#2r', domain)).to.be.an(Error);
- expect(domains.validateHostname('a%b', domain)).to.be.an(Error);
- expect(domains.validateHostname('ab_', domain)).to.be.an(Error);
- expect(domains.validateHostname('ab.', domain)).to.be.an(Error);
- expect(domains.validateHostname('ab..c', domain)).to.be.an(Error);
- expect(domains.validateHostname('.ab', domain)).to.be.an(Error);
- expect(domains.validateHostname('-ab', domain)).to.be.an(Error);
- expect(domains.validateHostname('ab-', domain)).to.be.an(Error);
- });
-
- it('total length cannot exceed 255', function () {
- var s = '';
- for (var i = 0; i < (255 - 'example.com'.length); i++) s += 's';
-
- expect(domains.validateHostname(s, domain)).to.be.an(Error);
- });
-
- it('allow valid domains', function () {
- expect(domains.validateHostname('a', domain)).to.be(null);
- expect(domains.validateHostname('a0-x', domain)).to.be(null);
- expect(domains.validateHostname('a0.x', domain)).to.be(null);
- expect(domains.validateHostname('a0.x.y', domain)).to.be(null);
- expect(domains.validateHostname('01', domain)).to.be(null);
- });
+ it('can add domain', async function () {
+ await domains.add(DOMAIN_0.domain, DOMAIN_0, auditSource);
});
- describe('getName', function () {
- it('works with zoneName==domain', function () {
- const d = {
- domain: 'example.com',
- zoneName: 'example.com',
- config: {}
- };
-
- expect(domains.getName(d, '', 'A')).to.be('');
- expect(domains.getName(d, 'www', 'A')).to.be('www');
- expect(domains.getName(d, 'www.dev', 'A')).to.be('www.dev');
-
- expect(domains.getName(d, '', 'MX')).to.be('');
-
- expect(domains.getName(d, '', 'TXT')).to.be('');
- expect(domains.getName(d, 'www', 'TXT')).to.be('www');
- expect(domains.getName(d, 'www.dev', 'TXT')).to.be('www.dev');
- });
-
- it('works when zoneName!=domain', function () {
- const d = {
- domain: 'dev.example.com',
- zoneName: 'example.com',
- config: {}
- };
-
- expect(domains.getName(d, '', 'A')).to.be('dev');
- expect(domains.getName(d, 'www', 'A')).to.be('www.dev');
- expect(domains.getName(d, 'www.dev', 'A')).to.be('www.dev.dev');
-
- expect(domains.getName(d, '', 'MX')).to.be('dev');
-
- expect(domains.getName(d, '', 'TXT')).to.be('dev');
- expect(domains.getName(d, 'www', 'TXT')).to.be('www.dev');
- expect(domains.getName(d, 'www.dev', 'TXT')).to.be('www.dev.dev');
- });
+ it('cannot add same domain twice', async function () {
+ const [error] = await safe(domains.add(DOMAIN_0.domain, DOMAIN_0, auditSource));
+ expect(error.reason).to.be(BoxError.ALREADY_EXISTS);
});
- describe('register', function () {
- let awsHostedZones;
+ it('can get domain', async function () {
+ const result = await domains.get(DOMAIN_0.domain);
+ expect(result.domain).to.equal(DOMAIN_0.domain);
+ expect(result.zoneName).to.equal(DOMAIN_0.zoneName);
+ expect(result.config).to.eql(DOMAIN_0.config);
+ });
- it('registers subdomain', function (done) {
- awsHostedZones = {
- HostedZones: [{
- Id: '/hostedzone/ZONEID',
- Name: `${domain.domain}.`,
- CallerReference: '305AFD59-9D73-4502-B020-F4E6F889CB30',
- ResourceRecordSetCount: 2,
- ChangeInfo: {
- Id: '/change/CKRTFJA0ANHXB',
- Status: 'INSYNC'
- }
- }],
- IsTruncated: false,
- MaxItems: '100'
- };
+ it('cannot get non-existent domain', async function () {
+ const result = await domains.get('random');
+ expect(result).to.be(null);
+ });
- nock.cleanAll();
+ it('can update domain', async function () {
+ const newConfig = {};
+ const newTlsConfig = { provider: 'letsencrypt-staging' };
+ const newDomain = Object.assign({}, DOMAIN_0, { config: newConfig, tlsConfig: newTlsConfig });
- let awsScope = nock('http://localhost:5353')
- .get('/2013-04-01/hostedzonesbyname?dnsname=example.com.&maxitems=1')
- .times(2)
- .reply(200, js2xml('ListHostedZonesResponse', awsHostedZones, { wrapHandlers: { HostedZones: () => 'HostedZone'} }))
- .get('/2013-04-01/hostedzone/ZONEID/rrset?maxitems=1&name=applocation.' + domain.domain + '.&type=A')
- .reply(200, js2xml('ListResourceRecordSetsResponse', { ResourceRecordSets: [ ] }, { 'Content-Type': 'application/xml' }))
- .post('/2013-04-01/hostedzone/ZONEID/rrset/')
- .reply(200, js2xml('ChangeResourceRecordSetsResponse', { ChangeInfo: { Id: 'RRID', Status: 'INSYNC' } }));
+ await domains.update(DOMAIN_0.domain, newDomain, auditSource);
- domains.registerLocations([ { subdomain: app.location, domain: app.domain } ], { overwriteDns: true }, (/*progress*/) => {}, function (error) {
- expect(error).to.be(null);
- expect(awsScope.isDone()).to.be.ok();
- done();
- });
- });
+ const result = await domains.get(DOMAIN_0.domain);
+ expect(result.domain).to.equal(DOMAIN_0.domain);
+ expect(result.zoneName).to.equal(DOMAIN_0.zoneName);
+ expect(result.provider).to.equal(DOMAIN_0.provider);
+ expect(result.config).to.eql(newConfig);
+ expect(result.tlsConfig).to.eql(newTlsConfig);
- it('unregisters subdomain', function (done) {
- nock.cleanAll();
+ DOMAIN_0.config = newConfig;
+ DOMAIN_0.tlsConfig = newTlsConfig;
+ });
- let awsScope = nock('http://localhost:5353')
- .get('/2013-04-01/hostedzonesbyname?dnsname=example.com.&maxitems=1')
- .reply(200, js2xml('ListHostedZonesResponse', awsHostedZones, { wrapHandlers: { HostedZones: () => 'HostedZone'} }))
- .post('/2013-04-01/hostedzone/ZONEID/rrset/')
- .reply(200, js2xml('ChangeResourceRecordSetsResponse', { ChangeInfo: { Id: 'RRID', Status: 'INSYNC' } }));
+ it('can get all domains', async function () {
+ const result = await domains.list();
+ expect(result.length).to.equal(2);
- domains.unregisterLocations([ { subdomain: app.location, domain: app.domain } ], (/*progress*/) => {}, function (error) {
- expect(error).to.be(null);
- expect(awsScope.isDone()).to.be.ok();
- done();
- });
- });
+ // sorted by domain
+ expect(result[0].domain).to.equal(domain.domain);
+ expect(result[0].zoneName).to.equal(domain.zoneName);
+ expect(result[0].provider).to.equal(domain.provider);
+ expect(result[0].config).to.eql(domain.config);
+ expect(result[0].tlsConfig).to.eql(domain.tlsConfig);
+
+ expect(result[1].domain).to.equal(DOMAIN_0.domain);
+ expect(result[1].zoneName).to.equal(DOMAIN_0.zoneName);
+ expect(result[1].provider).to.equal(DOMAIN_0.provider);
+ expect(result[1].config).to.eql(DOMAIN_0.config);
+ expect(result[1].tlsConfig).to.eql(DOMAIN_0.tlsConfig);
+ });
+
+ it('cannot delete non-existing domain', async function () {
+ const [error] = await safe(domains.del('not.exists', auditSource));
+ expect(error).to.be.a(BoxError);
+ expect(error.reason).to.equal(BoxError.NOT_FOUND);
+ });
+
+ it('cannot delete dashboard domain', async function () {
+ const [error] = await safe(domains.del(domain.domain, auditSource));
+ expect(error).to.be.a(BoxError);
+ expect(error.reason).to.equal(BoxError.CONFLICT);
+ expect(error.message).to.equal('Cannot remove admin domain');
+ });
+
+ it('cannot delete referenced domain', async function () {
+ const appCopy = Object.assign({}, app, { id: 'into', location: 'xx', domain: DOMAIN_0.domain, portBindings: {} });
+
+ await util.promisify(appdb.add)(appCopy.id, appCopy.appStoreId, appCopy.manifest, appCopy.location, appCopy.domain, appCopy.portBindings, appCopy);
+
+ const [error] = await safe(domains.del(DOMAIN_0.domain, auditSource));
+ expect(error.reason).to.equal(BoxError.CONFLICT);
+ expect(error.message).to.contain('Domain is in use by one or more app');
+
+ await util.promisify(appdb.del)(appCopy.id);
+ });
+
+ it('can delete existing domain', async function () {
+ await domains.del(DOMAIN_0.domain, auditSource);
+
+ const result = await domains.get(DOMAIN_0.domain);
+ expect(result).to.be(null);
});
});
diff --git a/src/test/reverseproxy-test.js b/src/test/reverseproxy-test.js
index e84759288..47ee349ab 100644
--- a/src/test/reverseproxy-test.js
+++ b/src/test/reverseproxy-test.js
@@ -12,7 +12,7 @@ const common = require('./common.js'),
paths = require('../paths.js'),
reverseProxy = require('../reverseproxy.js');
-describe('Certificates', function () {
+describe('Reverse Proxy', function () {
const { setup, cleanup, domain, auditSource, app } = common;
const domainCopy = Object.assign({}, domain);
@@ -141,10 +141,10 @@ describe('Certificates', function () {
});
describe('getApi - letsencrypt-prod', function () {
- before(function (done) {
+ before(async function () {
domainCopy.tlsConfig = { provider: 'letsencrypt-prod' };
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
});
it('returns prod acme in prod cloudron', async function () {
@@ -155,10 +155,10 @@ describe('Certificates', function () {
});
describe('getApi - letsencrypt-staging', function () {
- before(function (done) {
+ before(async function () {
domainCopy.tlsConfig = { provider: 'letsencrypt-staging' };
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
});
it('returns staging acme in prod cloudron', async function () {
@@ -169,10 +169,10 @@ describe('Certificates', function () {
});
describe('configureApp', function () {
- before(function (done) {
+ before(async function () {
domainCopy.tlsConfig = { provider: 'fallback' };
- domains.update(domainCopy.domain, domainCopy, auditSource, done);
+ await domains.update(domainCopy.domain, domainCopy, auditSource);
});
it('configure nginx correctly', function (done) {
diff --git a/src/wellknown.js b/src/wellknown.js
index fadf9304c..e311ba139 100644
--- a/src/wellknown.js
+++ b/src/wellknown.js
@@ -14,6 +14,7 @@ const assert = require('assert'),
util = require('util');
const MAIL_AUTOCONFIG_EJS = fs.readFileSync(__dirname + '/autoconfig.xml.ejs', { encoding: 'utf8' });
+const domainsGet = util.callbackify(domains.get);
function get(domain, location, callback) {
assert.strictEqual(typeof domain, 'string');
@@ -34,7 +35,7 @@ function get(domain, location, callback) {
} else if (location === 'host-meta' || location === 'matrix/server' || location === 'matrix/client') {
const type = location === 'host-meta' ? 'text/xml' : 'application/json';
- domains.get(domain, function (error, domainObject) {
+ domainsGet(domain, function (error, domainObject) {
if (error) return callback(error);
if (!domainObject.wellKnown || !(location in domainObject.wellKnown)) return callback(new BoxError(BoxError.NOT_FOUND, 'No custom well-known config'));