'use strict'; var request = require('@root/request'); request = require('util').promisify(request); var defaults = { baseUrl: 'https://api.digitalocean.com' }; module.exports.create = function(config) { // config = { baseUrl, token } var baseUrl = config.baseUrl || defaults.baseUrl; var authtoken = config.token; var helpers = { getDomain: function(identifier) { var url = baseUrl + '/v2/domains/'; return request({ method: 'GET', url: url, headers: { Authorization: 'Bearer ' + authtoken, 'Content-Type': 'application/json' }, json: true }) .then(function(resp) { return resp.body.domains.map(function(x) { return x.name; }); }) .then(function(domains) { for (var i = 0; i < domains.length; i++) { var domain = domains[i]; var pattern = new RegExp(domain.replace('.', '.') + '$'); if (pattern.test(identifier)) { return domain; } } throw new Error('Domain not found for: ' + identifier); }); }, getTXTRecord: function(data) { // data:{dnsPrefix:"_88-acme-challenge-0e.foo",zone:"example.com",txt:"_cdZWaclIbkP1qYpMkZIURTK--ydQIK6d9axFmftWz0"} var dnsPrefix = data.dnsPrefix; var txt = data.txt; var url = baseUrl + '/v2/domains/' + data.zone + '/records'; // Digital ocean provides the api to fetch records by ID. Since we do not have id, we fetch all the records, // filter the required TXT record return request({ method: 'GET', url: url, json: true, headers: { Authorization: 'Bearer ' + authtoken, 'Content-Type': 'application/json' } }).then(function(resp) { resp = resp.body; var entries = resp && resp.domain_records && resp.domain_records.filter(function(x) { return x.type === 'TXT' && x.name === dnsPrefix && x.data === txt; }); return entries && entries[0]; }); } }; return { set: function(data) { var ch = data.challenge; var domainRecord = ch.identifier.value; // Note: zone != domain // example.com, foo.example.com, and bar.foo.example.com are all in the example.com zone!! // Need to get list of "domains" (zones) and *then* set "subdomains" (domain records) // https://developers.digitalocean.com/documentation/v2/#domains return helpers.getDomain(domainRecord).then(function(zone) { // Note: dnsPrefix != dnsHost.split('.')[0] // _greenlock-dryrun-2277.bar.foo.example.com => _greenlock-dryrun-2277.bar.foo var dnsPrefix = ch.dnsHost.replace(new RegExp('.' + zone + '$'), ''); var txt = ch.dnsAuthorization; var url = baseUrl + '/v2/domains/' + zone + '/records'; // console.info('Adding TXT', data); return request({ method: 'POST', url: url, headers: { Authorization: 'Bearer ' + authtoken, 'Content-Type': 'application/json' }, json: { type: 'TXT', name: dnsPrefix, data: txt, // Note: set a LOW ttl so that responses are not cached so long ttl: 300 } }).then(function(resp) { resp = resp.body; if (resp && resp.domain_record && resp.domain_record.data === txt) { return true; } throw new Error('record did not set. check subdomain, api key, etc'); }); }); }, remove: function(data) { var ch = data.challenge; var domainRecord = ch.identifier.value; // Digital ocean provides the api to remove records by ID. So we first get the recordId and use it to remove the domain record return helpers.getDomain(domainRecord).then(function(zone) { var dnsPrefix = ch.dnsHost.replace(new RegExp('.' + zone + '$'), ''); // console.info('Removing TXT', data); var payload = { dnsPrefix: dnsPrefix, zone: zone, txt: ch.dnsAuthorization }; return helpers.getTXTRecord(payload).then(function(txtRecord) { if (txtRecord) { var url = baseUrl + '/v2/domains/' + zone + '/records/' + txtRecord.id; return request({ method: 'DELETE', url: url, headers: { Authorization: 'Bearer ' + authtoken, 'Content-Type': 'application/json' } }).then(function(resp) { resp = resp.body; return true; }); } else { throw new Error('Txt Record not found for removal'); } }); }); }, get: function(data) { var ch = data.challenge; var domainRecord = ch.identifier.value; return helpers.getDomain(domainRecord).then(function(zone) { var dnsPrefix = domainRecord.replace(new RegExp('.' + zone + '$'), ''); // console.info('Fetching TXT', data); var payload = { dnsPrefix: dnsPrefix, zone: zone, txt: ch.dnsAuthorization }; return helpers.getTXTRecord(payload).then(function(txtRecord) { if (txtRecord) { return { dnsAuthorization: txtRecord.data }; } else { return null; } }); }); } }; };