diff --git a/src/dns/cloudflare.js b/src/dns/cloudflare.js index 17f5982ea..68d2583b0 100644 --- a/src/dns/cloudflare.js +++ b/src/dns/cloudflare.js @@ -11,18 +11,19 @@ exports = module.exports = { }; const assert = require('assert'), - async = require('async'), BoxError = require('../boxerror.js'), constants = require('../constants.js'), debug = require('debug')('box:dns/cloudflare'), + dig = require('../dig.js'), dns = require('../dns.js'), + safe = require('safetydance'), superagent = require('superagent'), util = require('util'), waitForDns = require('./waitfordns.js'), _ = require('underscore'); // we are using latest v4 stable API https://api.cloudflare.com/#getting-started-endpoints -var CLOUDFLARE_ENDPOINT = 'https://api.cloudflare.com/client/v4'; +const CLOUDFLARE_ENDPOINT = 'https://api.cloudflare.com/client/v4'; function removePrivateFields(domainObject) { domainObject.config.token = constants.SECRET_PLACEHOLDER; @@ -33,12 +34,11 @@ function injectPrivateFields(newConfig, currentConfig) { if (newConfig.token === constants.SECRET_PLACEHOLDER) newConfig.token = currentConfig.token; } -function translateRequestError(result, callback) { +async function translateRequestError(result) { assert.strictEqual(typeof result, 'object'); - assert.strictEqual(typeof callback, 'function'); - if (result.statusCode === 404) return callback(new BoxError(BoxError.NOT_FOUND, util.format('%s %j', result.statusCode, 'API does not exist'))); - if (result.statusCode === 422) return callback(new BoxError(BoxError.BAD_FIELD, result.body.message)); + if (result.statusCode === 404) return new BoxError(BoxError.NOT_FOUND, util.format('%s %j', result.statusCode, 'API does not exist')); + if (result.statusCode === 422) return new BoxError(BoxError.BAD_FIELD, result.body.message); if (result.statusCode === 400 || result.statusCode === 401 || result.statusCode === 403) { let message = 'Unknown error'; if (typeof result.body.error === 'string') { @@ -47,10 +47,10 @@ function translateRequestError(result, callback) { let error = result.body.errors[0]; message = `message: ${error.message} statusCode: ${result.statusCode} code:${error.code}`; } - return callback(new BoxError(BoxError.ACCESS_DENIED, message)); + return new BoxError(BoxError.ACCESS_DENIED, message); } - callback(new BoxError(BoxError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body))); + return new BoxError(BoxError.EXTERNAL_ERROR, util.format('%s %j', result.statusCode, result.body)); } function createRequest(method, url, domainConfig) { @@ -58,8 +58,7 @@ function createRequest(method, url, domainConfig) { assert.strictEqual(typeof url, 'string'); assert.strictEqual(typeof domainConfig, 'object'); - let request = superagent(method, url) - .timeout(30 * 1000); + const request = superagent(method, url).timeout(30 * 1000).ok(() => true); if (domainConfig.tokenType === 'GlobalApiKey') { request.set('X-Auth-Key', domainConfig.token).set('X-Auth-Email', domainConfig.email); @@ -70,47 +69,39 @@ function createRequest(method, url, domainConfig) { return request; } -function getZoneByName(domainConfig, zoneName, callback) { +async function getZoneByName(domainConfig, zoneName) { assert.strictEqual(typeof domainConfig, 'object'); assert.strictEqual(typeof zoneName, 'string'); - assert.strictEqual(typeof callback, 'function'); - createRequest('GET', CLOUDFLARE_ENDPOINT + '/zones?name=' + zoneName + '&status=active', domainConfig) - .end(function (error, result) { - if (error && !error.response) return callback(error); - if (result.statusCode !== 200 || result.body.success !== true) return translateRequestError(result, callback); - if (!result.body.result.length) return callback(new BoxError(BoxError.NOT_FOUND, util.format('%s %j', result.statusCode, result.body))); + const [error, response] = await safe(createRequest('GET', `${CLOUDFLARE_ENDPOINT}/zones?name=${zoneName}&status=active`, domainConfig)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200 || response.body.success !== true) throw translateRequestError(response); + if (!response.body.result.length) throw new BoxError(BoxError.NOT_FOUND, util.format('%s %j', response.statusCode, response.body)); - callback(null, result.body.result[0]); - }); + return response.body.result[0]; } // gets records filtered by zone, type and fqdn -function getDnsRecords(domainConfig, zoneId, fqdn, type, callback) { +async function getDnsRecords(domainConfig, zoneId, fqdn, type) { assert.strictEqual(typeof domainConfig, 'object'); assert.strictEqual(typeof zoneId, 'string'); assert.strictEqual(typeof fqdn, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); - createRequest('GET', CLOUDFLARE_ENDPOINT + '/zones/' + zoneId + '/dns_records', domainConfig) - .query({ type: type, name: fqdn }) - .end(function (error, result) { - if (error && !error.response) return callback(error); - if (result.statusCode !== 200 || result.body.success !== true) return translateRequestError(result, callback); + const [error, response] = await safe(createRequest('GET', `${CLOUDFLARE_ENDPOINT}/zones/${zoneId}/dns_records`, domainConfig) + .query({ type: type, name: fqdn })); - var tmp = result.body.result; + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200 || response.body.success !== true) throw translateRequestError(response); - return callback(null, tmp); - }); + return response.body.result; } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -118,131 +109,98 @@ function upsert(domainObject, location, type, values, callback) { debug('upsert: %s for zone %s of type %s with values %j', fqdn, zoneName, type, values); - getZoneByName(domainConfig, zoneName, function(error, result) { - if (error) return callback(error); + const result = await getZoneByName(domainConfig, zoneName); + const zoneId = result.id; - let zoneId = result.id; + const records = await getDnsRecords(domainConfig, zoneId, fqdn, type); - getDnsRecords(domainConfig, zoneId, fqdn, type, function (error, dnsRecords) { - if (error) return callback(error); + let i = 0; // // used to track available records to update instead of create - let i = 0; // // used to track available records to update instead of create + for (let value of values) { + let priority = null; - async.eachSeries(values, function (value, iteratorCallback) { - var priority = null; + if (type === 'MX') { + priority = parseInt(value.split(' ')[0], 10); + value = value.split(' ')[1]; + } - if (type === 'MX') { - priority = parseInt(value.split(' ')[0], 10); - value = value.split(' ')[1]; - } + const data = { + type: type, + name: fqdn, + content: value, + priority: priority, + proxied: false, + ttl: 120 // 1 means "automatic" (meaning 300ms) and 120 is the lowest supported + }; - var data = { - type: type, - name: fqdn, - content: value, - priority: priority, - proxied: false, - ttl: 120 // 1 means "automatic" (meaning 300ms) and 120 is the lowest supported - }; + if (i >= records.length) { // create a new record + debug(`upsert: Adding new record fqdn: ${fqdn}, zoneName: ${zoneName} proxied: false`); - if (i >= dnsRecords.length) { // create a new record - debug(`upsert: Adding new record fqdn: ${fqdn}, zoneName: ${zoneName} proxied: false`); + const [error, response] = await safe(createRequest('POST', `${CLOUDFLARE_ENDPOINT}/zones/${zoneId}/dns_records`, domainConfig) + .send(data)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200 || response.body.success !== true) throw translateRequestError(response); + } else { // replace existing record + data.proxied = records[i].proxied; // preserve proxied parameter - createRequest('POST', CLOUDFLARE_ENDPOINT + '/zones/' + zoneId + '/dns_records', domainConfig) - .send(data) - .end(function (error, result) { - if (error && !error.response) return iteratorCallback(error); - if (result.statusCode !== 200 || result.body.success !== true) return translateRequestError(result, iteratorCallback); + debug(`upsert: Updating existing record fqdn: ${fqdn}, zoneName: ${zoneName} proxied: ${data.proxied}`); - iteratorCallback(null); - }); - } else { // replace existing record - data.proxied = dnsRecords[i].proxied; // preserve proxied parameter + const [error, response] = await safe(createRequest('PUT', `${CLOUDFLARE_ENDPOINT}/zones/${zoneId}/dns_records/${records[i].id}`, domainConfig) + .send(data)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200 || response.body.success !== true) throw translateRequestError(response); + ++i; // increment, as we have consumed the record + } + } - debug(`upsert: Updating existing record fqdn: ${fqdn}, zoneName: ${zoneName} proxied: ${data.proxied}`); - - createRequest('PUT', CLOUDFLARE_ENDPOINT + '/zones/' + zoneId + '/dns_records/' + dnsRecords[i].id, domainConfig) - .send(data) - .end(function (error, result) { - ++i; // increment, as we have consumed the record - - if (error && !error.response) return iteratorCallback(error); - if (result.statusCode !== 200 || result.body.success !== true) return translateRequestError(result, iteratorCallback); - - iteratorCallback(null); - }); - } - }, callback); - }); - }); + for (let j = values.length + 1; j < records.length; j++) { + const [error] = await safe(createRequest('DELETE', `${CLOUDFLARE_ENDPOINT}/zones/${zoneId}/dns_records/${records[j].id}`, domainConfig)); + if (error) debug(`upsert: error removing record ${records[j].id}: ${error.message}`); + } } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, fqdn = dns.fqdn(location, domainObject); - getZoneByName(domainConfig, zoneName, function(error, zone) { - if (error) return callback(error); - - getDnsRecords(domainConfig, zone.id, fqdn, type, function (error, result) { - if (error) return callback(error); - - var tmp = result.map(function (record) { return record.content; }); - debug('get: %j', tmp); - - callback(null, tmp); - }); - }); + const zone = await getZoneByName(domainConfig, zoneName); + const result = await getDnsRecords(domainConfig, zone.id, fqdn, type); + const tmp = result.map(function (record) { return record.content; }); + return tmp; } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, fqdn = dns.fqdn(location, domainObject); - getZoneByName(domainConfig, zoneName, function(error, zone) { - if (error) return callback(error); + const zone = await getZoneByName(domainConfig, zoneName); - getDnsRecords(domainConfig, zone.id, fqdn, type, function(error, result) { - if (error) return callback(error); - if (result.length === 0) return callback(null); + const result = await getDnsRecords(domainConfig, zone.id, fqdn, type); + if (result.length === 0) return; - const zoneId = result[0].zone_id; + const zoneId = result[0].zone_id; - const tmp = result.filter(function (record) { return values.some(function (value) { return value === record.content; }); }); - debug('del: %j', tmp); + const tmp = result.filter(function (record) { return values.some(function (value) { return value === record.content; }); }); + debug('del: %j', tmp); - if (tmp.length === 0) return callback(null); + if (tmp.length === 0) return; - async.eachSeries(tmp, function (record, callback) { - createRequest('DELETE', CLOUDFLARE_ENDPOINT + '/zones/'+ zoneId + '/dns_records/' + record.id, domainConfig) - .end(function (error, result) { - if (error && !error.response) return callback(error); - if (result.statusCode !== 200 || result.body.success !== true) return translateRequestError(result, callback); - - debug('del: done'); - - callback(null); - }); - }, function (error) { - if (error) return callback(error); - - callback(null, 'unused'); - }); - }); - }); + for (const r of tmp) { + const [error, response] = await safe(createRequest('DELETE', `${CLOUDFLARE_ENDPOINT}/zones/${zoneId}/dns_records/${r.id}`, domainConfig)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200 || response.body.success !== true) throw translateRequestError(response); + } } async function wait(domainObject, subdomain, type, value, options) { @@ -271,19 +229,18 @@ async function wait(domainObject, subdomain, type, value, options) { // maybe we can check for dns to be cloudflare IPs? https://api.cloudflare.com/#cloudflare-ips-cloudflare-ip-details } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName; // token can be api token or global api key - if (!domainConfig.token || typeof domainConfig.token !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'token must be a non-empty string', { field: 'token' })); - if (domainConfig.tokenType !== 'GlobalApiKey' && domainConfig.tokenType !== 'ApiToken') return callback(new BoxError(BoxError.BAD_FIELD, 'tokenType is required', { field: 'tokenType' })); + if (!domainConfig.token || typeof domainConfig.token !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'token must be a non-empty string'); + if (domainConfig.tokenType !== 'GlobalApiKey' && domainConfig.tokenType !== 'ApiToken') throw new BoxError(BoxError.BAD_FIELD, 'tokenType is required'); if (domainConfig.tokenType === 'GlobalApiKey') { - if (typeof domainConfig.email !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'email must be a non-empty string', { field: 'email' })); + if (typeof domainConfig.email !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'email must be a non-empty string'); } const ip = '127.0.0.1'; @@ -294,35 +251,26 @@ function verifyDomainConfig(domainObject, callback) { email: domainConfig.email || null }; - if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here + if (process.env.BOX_ENV === 'test') return credentials; // this shouldn't be here - dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { - if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain', { field: 'nameservers' })); - if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' })); + const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 })); + if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain'); + if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'); - getZoneByName(domainConfig, zoneName, function(error, zone) { - if (error) return callback(error); + const zone = await getZoneByName(domainConfig, zoneName); - if (!_.isEqual(zone.name_servers.sort(), nameservers.sort())) { - debug('verifyDomainConfig: %j and %j do not match', nameservers, zone.name_servers); - return callback(new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Cloudflare', { field: 'nameservers' })); - } + if (!_.isEqual(zone.name_servers.sort(), nameservers.sort())) { + debug('verifyDomainConfig: %j and %j do not match', nameservers, zone.name_servers); + throw new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Cloudflare'); + } - const location = 'cloudrontestdns'; + const location = 'cloudrontestdns'; - upsert(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); + await upsert(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record added'); - debug('verifyDomainConfig: Test A record added'); + await del(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record removed again'); - del(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); - - debug('verifyDomainConfig: Test A record removed again'); - - callback(null, credentials); - }); - }); - }); - }); + return credentials; } diff --git a/src/dns/gandi.js b/src/dns/gandi.js index 7c8cfabe7..7735ef26c 100644 --- a/src/dns/gandi.js +++ b/src/dns/gandi.js @@ -14,7 +14,9 @@ const assert = require('assert'), BoxError = require('../boxerror.js'), constants = require('../constants.js'), debug = require('debug')('box:dns/gandi'), + dig = require('../dig.js'), dns = require('../dns.js'), + safe = require('safetydance'), superagent = require('superagent'), util = require('util'), waitForDns = require('./waitfordns.js'); @@ -34,12 +36,11 @@ function injectPrivateFields(newConfig, currentConfig) { if (newConfig.token === constants.SECRET_PLACEHOLDER) newConfig.token = currentConfig.token; } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -47,30 +48,27 @@ function upsert(domainObject, location, type, values, callback) { debug(`upsert: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - var data = { + const data = { 'rrset_ttl': 300, // this is the minimum allowed 'rrset_values': values // for mx records, value is already of the ' ' format }; - superagent.put(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) + const [error, response] = await safe(superagent.put(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) .set('X-Api-Key', domainConfig.token) .timeout(30 * 1000) .send(data) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode === 400) return callback(new BoxError(BoxError.BAD_FIELD, formatError(result))); - if (result.statusCode !== 201) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - return callback(null); - }); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, formatError(response)); + if (response.statusCode !== 201) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -78,27 +76,24 @@ function get(domainObject, location, type, callback) { debug(`get: ${name} in zone ${zoneName} of type ${type}`); - superagent.get(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) + const [error, response] = await safe(superagent.get(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) .set('X-Api-Key', domainConfig.token) .timeout(30 * 1000) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode === 404) return callback(null, [ ]); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - debug('get: %j', result.body); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode === 404) return []; + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); - return callback(null, result.body.rrset_values); - }); + return response.body.rrset_values; } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -106,19 +101,15 @@ function del(domainObject, location, type, values, callback) { debug(`del: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - superagent.del(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) + const [error, response] = await safe(superagent.del(`${GANDI_API}/domains/${zoneName}/records/${name}/${type}`) .set('X-Api-Key', domainConfig.token) .timeout(30 * 1000) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 404) return callback(null); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode !== 204) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - debug('del: done'); - - return callback(null); - }); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 404) return; + if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode !== 204) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } async function wait(domainObject, subdomain, type, value, options) { @@ -133,46 +124,38 @@ async function wait(domainObject, subdomain, type, value, options) { await waitForDns(fqdn, domainObject.zoneName, type, value, options); } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName; - if (!domainConfig.token || typeof domainConfig.token !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'token must be a non-empty string', { field: 'token' })); + if (!domainConfig.token || typeof domainConfig.token !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'token must be a non-empty string'); - var credentials = { + const credentials = { token: domainConfig.token }; const ip = '127.0.0.1'; - if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here + if (process.env.BOX_ENV === 'test') return credentials; // this shouldn't be here - dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { - if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain', { field: 'nameservers' })); - if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' })); + const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 })); + if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain'); + if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'); - if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('.gandi.net') !== -1; })) { - debug('verifyDomainConfig: %j does not contain Gandi NS', nameservers); - return callback(new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Gandi', { field: 'nameservers' })); - } + if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('.gandi.net') !== -1; })) { + debug('verifyDomainConfig: %j does not contain Gandi NS', nameservers); + throw new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Gandi'); + } - const location = 'cloudrontestdns'; + const location = 'cloudrontestdns'; - upsert(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); + await upsert(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record added'); - debug('verifyDomainConfig: Test A record added'); + await del(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record removed again'); - del(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); - - debug('verifyDomainConfig: Test A record removed again'); - - callback(null, credentials); - }); - }); - }); + return credentials; } diff --git a/src/dns/godaddy.js b/src/dns/godaddy.js index a654c060f..ae275047e 100644 --- a/src/dns/godaddy.js +++ b/src/dns/godaddy.js @@ -14,7 +14,9 @@ const assert = require('assert'), BoxError = require('../boxerror.js'), constants = require('../constants.js'), debug = require('debug')('box:dns/godaddy'), + dig = require('../dig.js'), dns = require('../dns.js'), + safe = require('safetydance'), superagent = require('superagent'), util = require('util'), waitForDns = require('./waitfordns.js'); @@ -40,12 +42,11 @@ function injectPrivateFields(newConfig, currentConfig) { if (newConfig.apiSecret === constants.SECRET_PLACEHOLDER) newConfig.apiSecret = currentConfig.apiSecret; } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -53,9 +54,9 @@ function upsert(domainObject, location, type, values, callback) { debug(`upsert: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - var records = [ ]; - values.forEach(function (value) { - var record = { ttl: 600 }; // 600 is the min ttl + const records = []; + for (const value of values) { + const record = { ttl: 600 }; // 600 is the min ttl if (type === 'MX') { record.priority = parseInt(value.split(' ')[0], 10); @@ -65,28 +66,25 @@ function upsert(domainObject, location, type, values, callback) { } records.push(record); - }); + } - superagent.put(`${GODADDY_API}/${zoneName}/records/${type}/${name}`) + const [error, response] = await safe(superagent.put(`${GODADDY_API}/${zoneName}/records/${type}/${name}`) .set('Authorization', `sso-key ${domainConfig.apiKey}:${domainConfig.apiSecret}`) .timeout(30 * 1000) .send(records) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode === 400) return callback(new BoxError(BoxError.BAD_FIELD, formatError(result))); // no such zone - if (result.statusCode === 422) return callback(new BoxError(BoxError.BAD_FIELD, formatError(result))); // conflict - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - return callback(null); - }); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, formatError(response)); // no such zone + if (response.statusCode === 422) throw new BoxError(BoxError.BAD_FIELD, formatError(response)); // conflict + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -94,31 +92,28 @@ function get(domainObject, location, type, callback) { debug(`get: ${name} in zone ${zoneName} of type ${type}`); - superagent.get(`${GODADDY_API}/${zoneName}/records/${type}/${name}`) + const [error, response] = await safe(superagent.get(`${GODADDY_API}/${zoneName}/records/${type}/${name}`) .set('Authorization', `sso-key ${domainConfig.apiKey}:${domainConfig.apiSecret}`) .timeout(30 * 1000) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode === 404) return callback(null, [ ]); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - debug('get: %j', result.body); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode === 404) return []; + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); - var values = result.body.map(function (record) { return record.data; }); + const values = response.body.map(function (record) { return record.data; }); - if (values.length === 1 && values[0] === GODADDY_INVALID_IP) return callback(null, [ ]); // pretend this record doesn't exist + if (values.length === 1 && values[0] === GODADDY_INVALID_IP) return []; // pretend this record doesn't exist - return callback(null, values); - }); + return values; } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -126,34 +121,28 @@ function del(domainObject, location, type, values, callback) { debug(`get: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - if (type !== 'A' && type !== 'TXT') return callback(new BoxError(BoxError.EXTERNAL_ERROR, 'Record deletion is not supported by GoDaddy API')); + if (type !== 'A' && type !== 'AAAA' && type !== 'TXT') throw new BoxError(BoxError.EXTERNAL_ERROR, 'Record deletion is not supported by GoDaddy API'); // check if the record exists at all so that we don't insert the "Dead" record for no reason - get(domainObject, location, type, function (error, values) { - if (error) return callback(error); - if (values.length === 0) return callback(); + const existingRecords = await get(domainObject, location, type); + if (existingRecords.length === 0) return; - // godaddy does not have a delete API. so fill it up with an invalid IP that we can ignore in future get() - var records = [{ - ttl: 600, - data: type === 'A' ? GODADDY_INVALID_IP : GODADDY_INVALID_TXT - }]; + // godaddy does not have a delete API. so fill it up with an invalid IP that we can ignore in future get() + const records = [{ + ttl: 600, + data: type === 'A' ? GODADDY_INVALID_IP : GODADDY_INVALID_TXT + }]; - superagent.put(`${GODADDY_API}/${zoneName}/records/${type}/${name}`) - .set('Authorization', `sso-key ${domainConfig.apiKey}:${domainConfig.apiSecret}`) - .send(records) - .timeout(30 * 1000) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 404) return callback(null); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + const [error, response] = await safe(superagent.put(`${GODADDY_API}/${zoneName}/records/${type}/${name}`) + .set('Authorization', `sso-key ${domainConfig.apiKey}:${domainConfig.apiSecret}`) + .send(records) + .timeout(30 * 1000) + .ok(() => true)); - debug('del: done'); - - return callback(null); - }); - }); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 404) return; + if (response.statusCode === 403 || response.statusCode === 401) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } async function wait(domainObject, subdomain, type, value, options) { @@ -168,48 +157,40 @@ async function wait(domainObject, subdomain, type, value, options) { await waitForDns(fqdn, domainObject.zoneName, type, value, options); } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName; - if (!domainConfig.apiKey || typeof domainConfig.apiKey !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'apiKey must be a non-empty string', { field: 'apiKey' })); - if (!domainConfig.apiSecret || typeof domainConfig.apiSecret !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'apiSecret must be a non-empty string', { field: 'apiSecret' })); + if (!domainConfig.apiKey || typeof domainConfig.apiKey !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'apiKey must be a non-empty string'); + if (!domainConfig.apiSecret || typeof domainConfig.apiSecret !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'apiSecret must be a non-empty string'); const ip = '127.0.0.1'; - var credentials = { + const credentials = { apiKey: domainConfig.apiKey, apiSecret: domainConfig.apiSecret }; - if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here + if (process.env.BOX_ENV === 'test') return credentials; // this shouldn't be here - dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { - if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain', { field: 'nameservers' })); - if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' })); + const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 })); + if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain'); + if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'); - if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('.domaincontrol.com') !== -1; })) { - debug('verifyDomainConfig: %j does not contain GoDaddy NS', nameservers); - return callback(new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to GoDaddy', { field: 'nameservers' })); - } + if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('.domaincontrol.com') !== -1; })) { + debug('verifyDomainConfig: %j does not contain GoDaddy NS', nameservers); + throw new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to GoDaddy'); + } - const location = 'cloudrontestdns'; + const location = 'cloudrontestdns'; - upsert(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); + await upsert(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record added'); - debug('verifyDomainConfig: Test A record added'); + await del(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record removed again'); - del(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); - - debug('verifyDomainConfig: Test A record removed again'); - - callback(null, credentials); - }); - }); - }); + return credentials; } diff --git a/src/dns/manual.js b/src/dns/manual.js index 656ed99fd..27397868a 100644 --- a/src/dns/manual.js +++ b/src/dns/manual.js @@ -13,7 +13,9 @@ exports = module.exports = { const assert = require('assert'), BoxError = require('../boxerror.js'), debug = require('debug')('box:dns/manual'), + dig = require('../dig.js'), dns = require('../dns.js'), + safe = require('safetydance'), waitForDns = require('./waitfordns.js'); function removePrivateFields(domainObject) { @@ -25,35 +27,32 @@ function injectPrivateFields(newConfig, currentConfig) { } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); debug('upsert: %s for zone %s of type %s with values %j', location, domainObject.zoneName, type, values); - return callback(null); + return; } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); - callback(null, [ ]); // returning ip confuses apptask into thinking the entry already exists + return []; // returning ip confuses apptask into thinking the entry already exists } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); - return callback(); + return; } async function wait(domainObject, subdomain, type, value, options) { @@ -68,17 +67,16 @@ async function wait(domainObject, subdomain, type, value, options) { await waitForDns(fqdn, domainObject.zoneName, type, value, options); } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof callback, 'function'); const zoneName = domainObject.zoneName; // Very basic check if the nameservers can be fetched - dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { - if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain', { field: 'nameservers' })); - if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' })); + const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 })); + if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain'); + if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'); - callback(null, {}); - }); + return {}; } diff --git a/src/dns/namecom.js b/src/dns/namecom.js index 0af583f98..855df8e67 100644 --- a/src/dns/namecom.js +++ b/src/dns/namecom.js @@ -14,6 +14,7 @@ const assert = require('assert'), BoxError = require('../boxerror.js'), constants = require('../constants.js'), debug = require('debug')('box:dns/namecom'), + dig = require('../dig.js'), dns = require('../dns.js'), safe = require('safetydance'), superagent = require('superagent'), @@ -34,17 +35,16 @@ function injectPrivateFields(newConfig, currentConfig) { if (newConfig.token === constants.SECRET_PLACEHOLDER) newConfig.token = currentConfig.token; } -function addRecord(domainConfig, zoneName, name, type, values, callback) { +async function addRecord(domainConfig, zoneName, name, type, values) { assert.strictEqual(typeof domainConfig, 'object'); assert.strictEqual(typeof zoneName, 'string'); assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); debug(`add: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - var data = { + const data = { host: name, type: type, ttl: 300 // 300 is the lowest @@ -61,20 +61,18 @@ function addRecord(domainConfig, zoneName, name, type, values, callback) { data.answer = values[0]; } - superagent.post(`${NAMECOM_API}/domains/${zoneName}/records`) + const [error, response] = await safe(superagent.post(`${NAMECOM_API}/domains/${zoneName}/records`) .auth(domainConfig.username, domainConfig.token) .timeout(30 * 1000) .send(data) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - return callback(null, 'unused-id'); - }); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } -function updateRecord(domainConfig, zoneName, recordId, name, type, values, callback) { +async function updateRecord(domainConfig, zoneName, recordId, name, type, values) { assert.strictEqual(typeof domainConfig, 'object'); assert.strictEqual(typeof zoneName, 'string'); assert.strictEqual(typeof recordId, 'number'); @@ -85,7 +83,7 @@ function updateRecord(domainConfig, zoneName, recordId, name, type, values, call debug(`update:${recordId} on ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - var data = { + const data = { host: name, type: type, ttl: 300 // 300 is the lowest @@ -102,61 +100,55 @@ function updateRecord(domainConfig, zoneName, recordId, name, type, values, call data.answer = values[0]; } - superagent.put(`${NAMECOM_API}/domains/${zoneName}/records/${recordId}`) + const [error, response] = await safe(superagent.put(`${NAMECOM_API}/domains/${zoneName}/records/${recordId}`) .auth(domainConfig.username, domainConfig.token) .timeout(30 * 1000) .send(data) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - return callback(null); - }); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } -function getInternal(domainConfig, zoneName, name, type, callback) { +async function getInternal(domainConfig, zoneName, name, type) { assert.strictEqual(typeof domainConfig, 'object'); assert.strictEqual(typeof zoneName, 'string'); assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); debug(`getInternal: ${name} in zone ${zoneName} of type ${type}`); - superagent.get(`${NAMECOM_API}/domains/${zoneName}/records`) + const [error, response] = await safe(superagent.get(`${NAMECOM_API}/domains/${zoneName}/records`) .auth(domainConfig.username, domainConfig.token) .timeout(30 * 1000) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + .ok(() => true)); - // name.com does not return the correct content-type - result.body = safe.JSON.parse(result.text); - if (!result.body.records) result.body.records = []; + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); - result.body.records.forEach(function (r) { - // name.com api simply strips empty properties - r.host = r.host || ''; - }); + // name.com does not return the correct content-type + response.body = safe.JSON.parse(response.text); + if (!response.body.records) response.body.records = []; - var results = result.body.records.filter(function (r) { - return (r.host === name && r.type === type); - }); + response.body.records.forEach(function (r) { + // name.com api simply strips empty properties + r.host = r.host || ''; + }); - debug('getInternal: %j', results); + const results = response.body.records.filter(function (r) { + return (r.host === name && r.type === type); + }); - return callback(null, results); - }); + return results; } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -164,42 +156,31 @@ function upsert(domainObject, location, type, values, callback) { debug(`upsert: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - getInternal(domainConfig, zoneName, name, type, function (error, result) { - if (error) return callback(error); + const result = await getInternal(domainConfig, zoneName, name, type); + if (result.length === 0) return await addRecord(domainConfig, zoneName, name, type, values); - if (result.length === 0) return addRecord(domainConfig, zoneName, name, type, values, callback); - - return updateRecord(domainConfig, zoneName, result[0].id, name, type, values, callback); - }); + return await updateRecord(domainConfig, zoneName, result[0].id, name, type, values); } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, name = dns.getName(domainObject, location, type) || ''; - getInternal(domainConfig, zoneName, name, type, function (error, result) { - if (error) return callback(error); - - var tmp = result.map(function (record) { return record.answer; }); - - debug('get: %j', tmp); - - return callback(null, tmp); - }); + const result = await getInternal(domainConfig, zoneName, name, type); + const tmp = result.map(function (record) { return record.answer; }); + return tmp; } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -207,22 +188,16 @@ function del(domainObject, location, type, values, callback) { debug(`del: ${name} in zone ${zoneName} of type ${type} with values ${JSON.stringify(values)}`); - getInternal(domainConfig, zoneName, name, type, function (error, result) { - if (error) return callback(error); + const result = await getInternal(domainConfig, zoneName, name, type); + if (result.length === 0) return; - if (result.length === 0) return callback(); - - superagent.del(`${NAMECOM_API}/domains/${zoneName}/records/${result[0].id}`) - .auth(domainConfig.username, domainConfig.token) - .timeout(30 * 1000) - .end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode === 403) return callback(new BoxError(BoxError.ACCESS_DENIED, formatError(result))); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); - - return callback(null); - }); - }); + const [error, response] = await safe(superagent.del(`${NAMECOM_API}/domains/${zoneName}/records/${result[0].id}`) + .auth(domainConfig.username, domainConfig.token) + .timeout(30 * 1000) + .ok(() => true)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode === 403) throw new BoxError(BoxError.ACCESS_DENIED, formatError(response)); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } async function wait(domainObject, subdomain, type, value, options) { @@ -237,48 +212,40 @@ async function wait(domainObject, subdomain, type, value, options) { await waitForDns(fqdn, domainObject.zoneName, type, value, options); } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName; - if (typeof domainConfig.username !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'username must be a string', { field: 'username' })); - if (typeof domainConfig.token !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'token must be a string', { field: 'token' })); + if (typeof domainConfig.username !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'username must be a string'); + if (typeof domainConfig.token !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'token must be a string'); - var credentials = { + const credentials = { username: domainConfig.username, token: domainConfig.token }; const ip = '127.0.0.1'; - if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here + if (process.env.BOX_ENV === 'test') return credentials; // this shouldn't be here - dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { - if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain', { field: 'nameservers' })); - if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' })); + const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 })); + if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain'); + if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'); - if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('.name.com') !== -1; })) { - debug('verifyDomainConfig: %j does not contain Name.com NS', nameservers); - return callback(new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to name.com', { field: 'nameservers' })); - } + if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('.name.com') !== -1; })) { + debug('verifyDomainConfig: %j does not contain Name.com NS', nameservers); + throw new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to name.com'); + } - const location = 'cloudrontestdns'; + const location = 'cloudrontestdns'; - upsert(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); + await upsert(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record added'); - debug('verifyDomainConfig: Test A record added'); + await del(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record removed again'); - del(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); - - debug('verifyDomainConfig: Test A record removed again'); - - callback(null, credentials); - }); - }); - }); + return credentials; } diff --git a/src/dns/netcup.js b/src/dns/netcup.js index 361989f4e..9525f85f6 100644 --- a/src/dns/netcup.js +++ b/src/dns/netcup.js @@ -14,7 +14,9 @@ const assert = require('assert'), BoxError = require('../boxerror.js'), constants = require('../constants.js'), debug = require('debug')('box:dns/netcup'), + dig = require('../dig.js'), dns = require('../dns.js'), + safe = require('safetydance'), superagent = require('superagent'), util = require('util'), waitForDns = require('./waitfordns.js'); @@ -36,9 +38,8 @@ function injectPrivateFields(newConfig, currentConfig) { } // returns a api session id -function login(domainConfig, callback) { +async function login(domainConfig) { assert.strictEqual(typeof domainConfig, 'object'); - assert.strictEqual(typeof callback, 'function'); const data = { action: 'login', @@ -49,19 +50,17 @@ function login(domainConfig, callback) { } }; - superagent.post(API_ENDPOINT).send(data).end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + const [error, response] = await safe(superagent.post(API_ENDPOINT).send(data).ok(() => true)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); - callback(null, result.body.responsedata.apisessionid); - }); + return response.body.responsedata.apisessionid; } -function getAllRecords(domainConfig, apiSessionId, zoneName, callback) { +async function getAllRecords(domainConfig, apiSessionId, zoneName) { assert.strictEqual(typeof domainConfig, 'object'); assert.strictEqual(typeof apiSessionId, 'string'); assert.strictEqual(typeof zoneName, 'string'); - assert.strictEqual(typeof callback, 'function'); debug(`getAllRecords: getting dns records of ${zoneName}`); @@ -75,20 +74,18 @@ function getAllRecords(domainConfig, apiSessionId, zoneName, callback) { } }; - superagent.post(API_ENDPOINT).send(data).end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); + const [error, response] = await safe(superagent.post(API_ENDPOINT).send(data).ok(() => true)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); - callback(null, result.body.responsedata.dnsrecords || []); - }); + return response.body.responsedata.dnsrecords || []; } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -96,63 +93,55 @@ function upsert(domainObject, location, type, values, callback) { debug('upsert: %s for zone %s of type %s with values %j', name, zoneName, type, values); - login(domainConfig, function (error, apiSessionId) { - if (error) return callback(error); + const apiSessionId = await login(domainConfig); - getAllRecords(domainConfig, apiSessionId, zoneName, function (error, result) { - if (error) return callback(error); + const result = await getAllRecords(domainConfig, apiSessionId, zoneName); - let records = []; + let records = []; - values.forEach(function (value) { - // remove possible quotation - if (value.charAt(0) === '"') value = value.slice(1); - if (value.charAt(value.length -1) === '"') value = value.slice(0, -1); + values.forEach(function (value) { + // remove possible quotation + if (value.charAt(0) === '"') value = value.slice(1); + if (value.charAt(value.length -1) === '"') value = value.slice(0, -1); - let priority = null; - if (type === 'MX') { - priority = parseInt(value.split(' ')[0], 10); - value = value.split(' ')[1]; - } + let priority = null; + if (type === 'MX') { + priority = parseInt(value.split(' ')[0], 10); + value = value.split(' ')[1]; + } - let record = result.find(function (r) { return r.hostname === name && r.type === type; }); - if (!record) record = { hostname: name, type: type, destination: value, deleterecord: false }; - else record.destination = value; + let record = result.find(function (r) { return r.hostname === name && r.type === type; }); + if (!record) record = { hostname: name, type: type, destination: value, deleterecord: false }; + else record.destination = value; - if (priority !== null) record.priority = priority; + if (priority !== null) record.priority = priority; - records.push(record); - }); - - const data = { - action: 'updateDnsRecords', - param:{ - apikey: domainConfig.apiKey, - apisessionid: apiSessionId, - customernumber: domainConfig.customerNumber, - domainname: zoneName, - dnsrecordset: { - dnsrecords: records - } - } - }; - - superagent.post(API_ENDPOINT).send(data).end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); - if (result.body.statuscode !== 2000) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); - - callback(null); - }); - }); + records.push(record); }); + + const data = { + action: 'updateDnsRecords', + param:{ + apikey: domainConfig.apiKey, + apisessionid: apiSessionId, + customernumber: domainConfig.customerNumber, + domainname: zoneName, + dnsrecordset: { + dnsrecords: records + } + } + }; + + const [error, response] = await safe(superagent.post(API_ENDPOINT).send(data).ok(() => true)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); + if (response.body.statuscode !== 2000) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -160,24 +149,19 @@ function get(domainObject, location, type, callback) { debug('get: %s for zone %s of type %s', name, zoneName, type); - login(domainConfig, function (error, apiSessionId) { - if (error) return callback(error); + const apiSessionId = await login(domainConfig); - getAllRecords(domainConfig, apiSessionId, zoneName, function (error, result) { - if (error) return callback(error); + const result = await getAllRecords(domainConfig, apiSessionId, zoneName); - // We only return the value string - callback(null, result.filter(function (r) { return r.hostname === name && r.type === type; }).map(function (r) { return r.destination; })); - }); - }); + // We only return the value string + return result.filter(function (r) { return r.hostname === name && r.type === type; }).map(function (r) { return r.destination; }); } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName, @@ -185,51 +169,44 @@ function del(domainObject, location, type, values, callback) { debug('del: %s for zone %s of type %s with values %j', name, zoneName, type, values); - login(domainConfig, function (error, apiSessionId) { - if (error) return callback(error); + const apiSessionId = await login(domainConfig); - getAllRecords(domainConfig, apiSessionId, zoneName, function (error, result) { - if (error) return callback(error); + const result = await getAllRecords(domainConfig, apiSessionId, zoneName); - let records = []; + let records = []; - values.forEach(function (value) { - // remove possible quotation - if (value.charAt(0) === '"') value = value.slice(1); - if (value.charAt(value.length -1) === '"') value = value.slice(0, -1); + values.forEach(function (value) { + // remove possible quotation + if (value.charAt(0) === '"') value = value.slice(1); + if (value.charAt(value.length -1) === '"') value = value.slice(0, -1); - let record = result.find(function (r) { return r.hostname === name && r.type === type && r.destination === value; }); - if (!record) return; + let record = result.find(function (r) { return r.hostname === name && r.type === type && r.destination === value; }); + if (!record) return; - record.deleterecord = true; + record.deleterecord = true; - records.push(record); - }); - - if (records.length === 0) return callback(null); - - const data = { - action: 'updateDnsRecords', - param:{ - apikey: domainConfig.apiKey, - apisessionid: apiSessionId, - customernumber: domainConfig.customerNumber, - domainname: zoneName, - dnsrecordset: { - dnsrecords: records - } - } - }; - - superagent.post(API_ENDPOINT).send(data).end(function (error, result) { - if (error && !error.response) return callback(new BoxError(BoxError.NETWORK_ERROR, error.message)); - if (result.statusCode !== 200) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); - if (result.body.statuscode !== 2000) return callback(new BoxError(BoxError.EXTERNAL_ERROR, formatError(result))); - - callback(null); - }); - }); + records.push(record); }); + + if (records.length === 0) return; + + const data = { + action: 'updateDnsRecords', + param:{ + apikey: domainConfig.apiKey, + apisessionid: apiSessionId, + customernumber: domainConfig.customerNumber, + domainname: zoneName, + dnsrecordset: { + dnsrecords: records + } + } + }; + + const [error, response] = await safe(superagent.post(API_ENDPOINT).send(data).ok(() => true)); + if (error) throw new BoxError(BoxError.NETWORK_ERROR, error.message); + if (response.statusCode !== 200) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); + if (response.body.statuscode !== 2000) throw new BoxError(BoxError.EXTERNAL_ERROR, formatError(response)); } async function wait(domainObject, subdomain, type, value, options) { @@ -244,50 +221,42 @@ async function wait(domainObject, subdomain, type, value, options) { await waitForDns(fqdn, domainObject.zoneName, type, value, options); } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); - assert.strictEqual(typeof callback, 'function'); const domainConfig = domainObject.config, zoneName = domainObject.zoneName; - if (!domainConfig.customerNumber || typeof domainConfig.customerNumber !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'customerNumber must be a non-empty string', { field: 'customerNumber' })); - if (!domainConfig.apiKey || typeof domainConfig.apiKey !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'apiKey must be a non-empty string', { field: 'apiKey' })); - if (!domainConfig.apiPassword || typeof domainConfig.apiPassword !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'apiPassword must be a non-empty string', { field: 'apiPassword' })); + if (!domainConfig.customerNumber || typeof domainConfig.customerNumber !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'customerNumber must be a non-empty string'); + if (!domainConfig.apiKey || typeof domainConfig.apiKey !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'apiKey must be a non-empty string'); + if (!domainConfig.apiPassword || typeof domainConfig.apiPassword !== 'string') throw new BoxError(BoxError.BAD_FIELD, 'apiPassword must be a non-empty string'); const ip = '127.0.0.1'; - var credentials = { + const credentials = { customerNumber: domainConfig.customerNumber, apiKey: domainConfig.apiKey, apiPassword: domainConfig.apiPassword, }; - if (process.env.BOX_ENV === 'test') return callback(null, credentials); // this shouldn't be here + if (process.env.BOX_ENV === 'test') return credentials; // this shouldn't be here - dns.resolve(zoneName, 'NS', { timeout: 5000 }, function (error, nameservers) { - if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain', { field: 'nameservers' })); - if (error || !nameservers) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers', { field: 'nameservers' })); + const [error, nameservers] = await safe(dig.resolve(zoneName, 'NS', { timeout: 5000 })); + if (error && error.code === 'ENOTFOUND') throw new BoxError(BoxError.BAD_FIELD, 'Unable to resolve nameservers for this domain'); + if (error || !nameservers) throw new BoxError(BoxError.BAD_FIELD, error ? error.message : 'Unable to get nameservers'); - if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('dns.netcup.net') !== -1; })) { - debug('verifyDomainConfig: %j does not contains Netcup NS', nameservers); - return callback(new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Netcup', { field: 'nameservers' })); - } + if (!nameservers.every(function (n) { return n.toLowerCase().indexOf('dns.netcup.net') !== -1; })) { + debug('verifyDomainConfig: %j does not contains Netcup NS', nameservers); + throw new BoxError(BoxError.BAD_FIELD, 'Domain nameservers are not set to Netcup'); + } - const location = 'cloudrontestdns'; + const location = 'cloudrontestdns'; - upsert(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); + await upsert(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record added'); - debug('verifyDomainConfig: Test A record added'); + await del(domainObject, location, 'A', [ ip ]); + debug('verifyDomainConfig: Test A record removed again'); - del(domainObject, location, 'A', [ ip ], function (error) { - if (error) return callback(error); - - debug('verifyDomainConfig: Test A record removed again'); - - callback(null, credentials); - }); - }); - }); + return credentials; } diff --git a/src/dns/noop.js b/src/dns/noop.js index 9222b6470..8ae563e50 100644 --- a/src/dns/noop.js +++ b/src/dns/noop.js @@ -21,35 +21,32 @@ function removePrivateFields(domainObject) { function injectPrivateFields(newConfig, currentConfig) { } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); debug('upsert: %s for zone %s of type %s with values %j', location, domainObject.zoneName, type, values); - return callback(null); + return; } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); - callback(null, [ ]); // returning ip confuses apptask into thinking the entry already exists + return []; // returning ip confuses apptask into thinking the entry already exists } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); - return callback(); + return; } async function wait(domainObject, location, type, value, options) { @@ -62,9 +59,8 @@ async function wait(domainObject, location, type, value, options) { // do nothing } -function verifyDomainConfig(domainObject, callback) { +async function verifyDomainConfig(domainObject) { assert.strictEqual(typeof domainObject, 'object'); - assert.strictEqual(typeof callback, 'function'); - return callback(null, { }); + return {}; } diff --git a/src/dns/wildcard.js b/src/dns/wildcard.js index bdf6ebe4f..5ad1b7ea4 100644 --- a/src/dns/wildcard.js +++ b/src/dns/wildcard.js @@ -28,35 +28,32 @@ function removePrivateFields(domainObject) { function injectPrivateFields(newConfig, currentConfig) { } -function upsert(domainObject, location, type, values, callback) { +async function upsert(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); debug('upsert: %s for zone %s of type %s with values %j', location, domainObject.zoneName, type, values); - return callback(null); + return; } -function get(domainObject, location, type, callback) { +async function get(domainObject, location, type) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); - assert.strictEqual(typeof callback, 'function'); - callback(null, [ ]); // returning ip confuses apptask into thinking the entry already exists + return []; // returning ip confuses apptask into thinking the entry already exists } -function del(domainObject, location, type, values, callback) { +async function del(domainObject, location, type, values) { assert.strictEqual(typeof domainObject, 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof type, 'string'); assert(Array.isArray(values)); - assert.strictEqual(typeof callback, 'function'); - return callback(); + return; } async function wait(domainObject, subdomain, type, value, options) {