'use strict'; var v4 = require('aws-signature-v4'); var xml2js = require('xml2js'); var parseString = xml2js.parseString; parseString = require('util').promisify(parseString); var builder = new xml2js.Builder(); var request; var defaults = { baseUri: '/2013-04-01/' }; function setWeightedPolicy(XmlObject, isWildcard) { if (isWildcard) { XmlObject.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [ 'wildcard' ]; } else { XmlObject.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].SetIdentifier = [ 'subdomain' ]; } XmlObject.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Weight = [ 1 ]; } // used to build XML payload var XmlParameters = { ChangeResourceRecordSetsRequest: { $: { xmlns: '' }, ChangeBatch: [ { Changes: [ { Change: [ { Action: [''], ResourceRecordSet: [ { Type: ['TXT'], Name: [''], ResourceRecords: [ { ResourceRecord: [ { Value: [''] } ] } ], TTL: [300] } ] } ] } ] } ] } }; module.exports.create = function(config) { var baseUri = defaults.baseUri.replace(/\/$/, ''); var key = config.key; var secret = config.secret; function api(method, path, body, query) { var body = body || ''; var query = query || {}; var options = { key: key, secret: secret, query: query }; var parameters = { method: method }; if (method === 'POST') { options.headers = { 'Content-Type': 'application/xml' }; parameters.headers = { 'Content-Type': 'application/xml' }; } if (body) { parameters.body = body; } var url = v4.createPresignedURL( method, 'route53.amazonaws.com', path, 'route53', body, options ); parameters.url = url; return request(parameters).then(function(response) { if (response.statusCode < 200 || response.statusCode >= 300) { console.error(response.statusCode, options.url); console.error(); console.error('Request:'); console.error(options); console.error(); console.error('Response:'); console.error(response.body); console.error(); throw new Error( 'Error response. Check token, baseUri, domains, etc.' ); } return response; }); } return { init: function(options) { request = options.request; return null; }, zones: function(data) { return api('GET', baseUri + '/' + 'hostedzone').then(function( response ) { return parseString(response.body).then(function(body) { var zones = body.ListHostedZonesResponse.HostedZones[0].HostedZone.map( function(element) { return element.Name[0].replace(/\.$/, ''); // all route 53 domains terminate with a dot } ); return zones; }); }); }, set: function(data) { var challenge = data.challenge; if (!challenge.dnsZone) { throw new Error('No matching zone for ' + challenge.dnsHost); } return api( 'GET', baseUri + '/' + 'hostedzonesbyname', null, 'dnsname=' + challenge.dnsZone + '&maxitems=1' ) .then(function(response) { return parseString(response.body).then(function(body) { return body; }); }) .then(function(body) { var zoneID = body.ListHostedZonesByNameResponse.HostedZones[0] .HostedZone[0].Id[0]; var xmlns = body.ListHostedZonesByNameResponse.$.xmlns; var parameters = JSON.parse(JSON.stringify(XmlParameters)); parameters.ChangeResourceRecordSetsRequest.$.xmlns = xmlns; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].Action[0] = 'CREATE'; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Name[0] = challenge.dnsHost; // AWS requires FQDN parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].ResourceRecords[0].ResourceRecord[0].Value[0] = '"' + challenge.dnsAuthorization + '"'; // value must be surrounded by double quotes // By default AWS creates record sets with simple routing policy, so duplicates are not allowed. // A workaround is put the record sets in different weighted policies. if (challenge.identifier.value.startsWith('foo')) { setWeightedPolicy(parameters, challenge.wildcard); } var xml = builder.buildObject(parameters); return api('POST', baseUri + zoneID + '/rrset/', xml); }); }, get: function(data) { var challenge = data.challenge; return api( 'GET', baseUri + '/' + 'hostedzonesbyname', null, 'dnsname=' + challenge.dnsZone + '&maxitems=1' ) .then(function(response) { return parseString(response.body).then(function(body) { return body; }); }) .then(function(body) { var zoneID = body.ListHostedZonesByNameResponse.HostedZones[0] .HostedZone[0].Id[0]; return zoneID; }) .then(function(zoneID) { return api( 'GET', baseUri + '/' + zoneID + '/rrset', null, 'name=' + challenge.dnsPrefix + '.' + challenge.dnsZone + '.' + '&type=TXT' ).then(function(response) { return parseString(response.body).then(function(body) { if ( body.ListResourceRecordSetsResponse .ResourceRecordSets[0] === '' ) { return null; } var record = body.ListResourceRecordSetsResponse.ResourceRecordSets[0].ResourceRecordSet.filter( function(element) { return ( element.ResourceRecords[0].ResourceRecord[0].Value[0].replace( /\"/g, '' ) === challenge.dnsAuthorization && element.Name[0].includes( challenge.dnsPrefix ) ); } )[0]; if (record) { return { dnsAuthorization: record.ResourceRecords[0].ResourceRecord[0].Value[0].replace( /\"/g, '' ) }; } return null; }); }); }); }, remove: function(data) { var challenge = data.challenge; return api( 'GET', baseUri + '/' + 'hostedzonesbyname', null, 'dnsname=' + challenge.dnsZone + '&maxitems=1' ) .then(function(response) { return parseString(response.body).then(function(body) { return body; }); }) .then(function(body) { var zoneID = body.ListHostedZonesByNameResponse.HostedZones[0] .HostedZone[0].Id[0]; var xmlns = body.ListHostedZonesByNameResponse.$.xmlns; var parameters = JSON.parse(JSON.stringify(XmlParameters)); parameters.ChangeResourceRecordSetsRequest.$.xmlns = xmlns; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].Action[0] = 'DELETE'; parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].Name[0] = challenge.dnsHost; // AWS requires FQDN parameters.ChangeResourceRecordSetsRequest.ChangeBatch[0].Changes[0].Change[0].ResourceRecordSet[0].ResourceRecords[0].ResourceRecord[0].Value[0] = '"' + challenge.dnsAuthorization + '"'; // value must be surrounded by double quotes // need to add weight and setidentifier to delete record set if (challenge.identifier.value.startsWith('foo')) { setWeightedPolicy(parameters, challenge.wildcard); } var xml = builder.buildObject(parameters); return api('POST', baseUri + zoneID + '/rrset/', xml); }); } }; };