2017-04-26 08:45:20 +04:00
'use strict' ;
exports = module . exports = {
upsert : upsert ,
get : get ,
del : del ,
2017-04-26 11:48:31 +02:00
waitForDns : require ( './waitfordns.js' ) ,
2017-04-26 08:45:20 +04:00
verifyDnsConfig : verifyDnsConfig
} ;
var assert = require ( 'assert' ) ,
2017-04-26 09:59:08 +02:00
async = require ( 'async' ) ,
2017-10-29 01:48:55 +02:00
debug = require ( 'debug' ) ( 'box:dns/cloudflare' ) ,
2018-02-08 10:21:31 -08:00
dns = require ( '../native-dns.js' ) ,
2018-04-29 11:20:12 -07:00
DomainsError = require ( '../domains.js' ) . DomainsError ,
2017-04-26 08:45:20 +04:00
superagent = require ( 'superagent' ) ,
2017-10-29 01:48:55 +02:00
util = require ( 'util' ) ,
_ = require ( 'underscore' ) ;
2017-04-26 08:45:20 +04:00
2017-04-26 11:48:31 +02:00
// we are using latest v4 stable API https://api.cloudflare.com/#getting-started-endpoints
2017-07-28 16:12:41 +02:00
var CLOUDFLARE _ENDPOINT = 'https://api.cloudflare.com/client/v4' ;
2017-04-26 08:45:20 +04:00
2017-07-31 11:25:17 +02:00
function translateRequestError ( result , callback ) {
assert . strictEqual ( typeof result , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-29 11:20:12 -07:00
if ( result . statusCode === 404 ) return callback ( new DomainsError ( DomainsError . NOT _FOUND , util . format ( '%s %j' , result . statusCode , 'API does not exist' ) ) ) ;
if ( result . statusCode === 422 ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , result . body . message ) ) ;
2017-07-31 11:25:17 +02:00
if ( ( result . statusCode === 400 || result . statusCode === 401 || result . statusCode === 403 ) && result . body . errors . length > 0 ) {
let error = result . body . errors [ 0 ] ;
2018-09-06 12:26:11 -07:00
let message = ` message: ${ error . message } statusCode: ${ result . statusCode } code: ${ error . code } ` ;
2017-07-31 11:25:17 +02:00
if ( error . code === 6003 ) {
if ( error . error _chain [ 0 ] && error . error _chain [ 0 ] . code === 6103 ) message = 'Invalid API Key' ;
else message = 'Invalid credentials' ;
2018-09-07 11:46:10 -07:00
} else if ( error . error _chain [ 0 ] && error . error _chain [ 0 ] . message ) {
message = message + ' ' + error . error _chain [ 0 ] . message ;
2017-07-31 11:25:17 +02:00
}
2018-04-29 11:20:12 -07:00
return callback ( new DomainsError ( DomainsError . ACCESS _DENIED , message ) ) ;
2017-07-31 11:25:17 +02:00
}
2018-04-29 11:20:12 -07:00
callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , util . format ( '%s %j' , result . statusCode , result . body ) ) ) ;
2017-07-31 11:25:17 +02:00
}
2017-04-26 08:45:20 +04:00
function getZoneByName ( dnsConfig , zoneName , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-04-26 11:48:31 +02:00
2017-04-26 08:45:20 +04:00
superagent . get ( CLOUDFLARE _ENDPOINT + '/zones?name=' + zoneName + '&status=active' )
2018-01-19 09:55:27 -08:00
. set ( 'X-Auth-Key' , dnsConfig . token )
. set ( 'X-Auth-Email' , dnsConfig . email )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( error ) ;
if ( result . statusCode !== 200 || result . body . success !== true ) return translateRequestError ( result , callback ) ;
2018-04-29 11:20:12 -07:00
if ( ! result . body . result . length ) return callback ( new DomainsError ( DomainsError . NOT _FOUND , util . format ( '%s %j' , result . statusCode , result . body ) ) ) ;
2018-01-19 09:55:27 -08:00
callback ( null , result . body . result [ 0 ] ) ;
} ) ;
2017-04-26 08:45:20 +04:00
}
2018-02-08 12:05:29 -08:00
function getDnsRecordsByZoneId ( dnsConfig , zoneId , zoneName , subdomain , type , callback ) {
2017-04-26 08:45:20 +04:00
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
2017-07-28 16:41:00 +02:00
assert . strictEqual ( typeof zoneId , 'string' ) ;
2017-04-26 08:45:20 +04:00
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-04-26 11:48:31 +02:00
2017-09-05 16:07:14 -07:00
var fqdn = subdomain === '' ? zoneName : subdomain + '.' + zoneName ;
2017-07-28 16:41:00 +02:00
superagent . get ( CLOUDFLARE _ENDPOINT + '/zones/' + zoneId + '/dns_records' )
2018-01-19 09:55:27 -08:00
. set ( 'X-Auth-Key' , dnsConfig . token )
. set ( 'X-Auth-Email' , dnsConfig . email )
. query ( { type : type , name : fqdn } )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( error ) ;
if ( result . statusCode !== 200 || result . body . success !== true ) return translateRequestError ( result , callback ) ;
2017-07-28 16:41:00 +02:00
2018-01-19 09:55:27 -08:00
var tmp = result . body . result ;
2017-04-26 08:45:20 +04:00
2018-01-19 09:55:27 -08:00
return callback ( null , tmp ) ;
} ) ;
2017-04-26 08:45:20 +04:00
}
function upsert ( dnsConfig , zoneName , subdomain , type , values , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert ( util . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var fqdn = subdomain === '' ? zoneName : subdomain + '.' + zoneName ;
debug ( 'upsert: %s for zone %s of type %s with values %j' , subdomain , zoneName , type , values ) ;
2017-07-28 16:41:00 +02:00
getZoneByName ( dnsConfig , zoneName , function ( error , result ) {
2017-04-26 08:45:20 +04:00
if ( error ) return callback ( error ) ;
2017-07-28 16:41:00 +02:00
var zoneId = result . id ;
2017-04-26 08:45:20 +04:00
2018-02-08 12:05:29 -08:00
getDnsRecordsByZoneId ( dnsConfig , zoneId , zoneName , subdomain , type , function ( error , result ) {
2017-04-26 08:45:20 +04:00
if ( error ) return callback ( error ) ;
2017-07-28 16:41:00 +02:00
var dnsRecords = result ;
// used to track available records to update instead of create
var i = 0 ;
async . eachSeries ( values , function ( value , callback ) {
2017-08-26 15:54:34 -07:00
var priority = null ;
if ( type === 'MX' ) {
2018-09-07 11:44:31 -07:00
priority = parseInt ( value . split ( ' ' ) [ 0 ] , 10 ) ;
2017-08-26 15:54:34 -07:00
value = value . split ( ' ' ) [ 1 ] ;
}
2017-07-28 16:41:00 +02:00
var data = {
type : type ,
name : fqdn ,
content : value ,
2017-08-26 15:54:34 -07:00
priority : priority ,
2017-07-28 16:41:00 +02:00
ttl : 120 // 1 means "automatic" (meaning 300ms) and 120 is the lowest supported
} ;
if ( i >= dnsRecords . length ) {
superagent . post ( CLOUDFLARE _ENDPOINT + '/zones/' + zoneId + '/dns_records' )
2018-01-19 09:55:27 -08:00
. set ( 'X-Auth-Key' , dnsConfig . token )
. set ( 'X-Auth-Email' , dnsConfig . email )
. send ( data )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( error ) ;
if ( result . statusCode !== 200 || result . body . success !== true ) return translateRequestError ( result , callback ) ;
callback ( null ) ;
} ) ;
2017-07-28 16:41:00 +02:00
} else {
superagent . put ( CLOUDFLARE _ENDPOINT + '/zones/' + zoneId + '/dns_records/' + dnsRecords [ i ] . id )
2018-01-19 09:55:27 -08:00
. set ( 'X-Auth-Key' , dnsConfig . token )
. set ( 'X-Auth-Email' , dnsConfig . email )
. send ( data )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
2017-07-28 16:41:00 +02:00
// increment, as we have consumed the record
2018-01-19 09:55:27 -08:00
++ i ;
2017-07-28 16:41:00 +02:00
2018-01-19 09:55:27 -08:00
if ( error && ! error . response ) return callback ( error ) ;
if ( result . statusCode !== 200 || result . body . success !== true ) return translateRequestError ( result , callback ) ;
2017-07-28 16:41:00 +02:00
2018-01-19 09:55:27 -08:00
callback ( null ) ;
} ) ;
2017-07-28 16:41:00 +02:00
}
2018-06-29 22:25:34 +02:00
} , callback ) ;
2017-04-26 08:45:20 +04:00
} ) ;
2017-04-26 09:59:08 +02:00
} ) ;
2017-04-26 08:45:20 +04:00
}
function get ( dnsConfig , zoneName , subdomain , type , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-07-28 16:41:00 +02:00
getZoneByName ( dnsConfig , zoneName , function ( error , result ) {
2017-04-26 08:45:20 +04:00
if ( error ) return callback ( error ) ;
2018-02-08 12:05:29 -08:00
getDnsRecordsByZoneId ( dnsConfig , result . id , zoneName , subdomain , type , function ( error , result ) {
2017-07-28 16:41:00 +02:00
if ( error ) return callback ( error ) ;
var tmp = result . map ( function ( record ) { return record . content ; } ) ;
debug ( 'get: %j' , tmp ) ;
2017-04-26 08:45:20 +04:00
2017-07-28 16:41:00 +02:00
callback ( null , tmp ) ;
} ) ;
2017-04-26 08:45:20 +04:00
} ) ;
}
function del ( dnsConfig , zoneName , subdomain , type , values , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert ( util . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-04-26 09:59:08 +02:00
2017-07-28 16:41:00 +02:00
getZoneByName ( dnsConfig , zoneName , function ( error , result ) {
2017-04-26 08:45:20 +04:00
if ( error ) return callback ( error ) ;
2018-02-08 12:05:29 -08:00
getDnsRecordsByZoneId ( dnsConfig , result . id , zoneName , subdomain , type , function ( error , result ) {
2017-07-28 16:41:00 +02:00
if ( error ) return callback ( error ) ;
if ( result . length === 0 ) return callback ( null ) ;
var zoneId = result [ 0 ] . zone _id ;
var tmp = result . filter ( function ( record ) { return values . some ( function ( value ) { return value === record . content ; } ) ; } ) ;
debug ( 'del: %j' , tmp ) ;
2017-04-26 08:45:20 +04:00
2017-07-28 16:41:00 +02:00
if ( tmp . length === 0 ) return callback ( null ) ;
2017-04-26 08:45:20 +04:00
2017-07-28 16:41:00 +02:00
async . eachSeries ( tmp , function ( record , callback ) {
superagent . del ( CLOUDFLARE _ENDPOINT + '/zones/' + zoneId + '/dns_records/' + record . id )
2018-01-19 09:55:27 -08:00
. set ( 'X-Auth-Key' , dnsConfig . token )
. set ( 'X-Auth-Email' , dnsConfig . email )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( error ) ;
2018-01-23 14:52:39 -08:00
if ( result . statusCode !== 200 || result . body . success !== true ) return translateRequestError ( result , callback ) ;
2017-04-26 08:45:20 +04:00
2018-01-19 09:55:27 -08:00
debug ( 'del: done' ) ;
2017-04-26 08:45:20 +04:00
2018-01-19 09:55:27 -08:00
callback ( null ) ;
} ) ;
2017-07-28 16:41:00 +02:00
} , function ( error ) {
if ( error ) return callback ( error ) ;
2017-04-26 08:45:20 +04:00
2017-07-28 16:41:00 +02:00
callback ( null , 'unused' ) ;
2017-04-26 08:45:20 +04:00
} ) ;
} ) ;
} ) ;
}
2017-07-28 16:33:19 +02:00
function verifyDnsConfig ( dnsConfig , fqdn , zoneName , ip , callback ) {
2017-04-26 08:45:20 +04:00
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
2017-07-28 16:33:19 +02:00
assert . strictEqual ( typeof fqdn , 'string' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
2017-04-26 08:45:20 +04:00
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-29 11:20:12 -07:00
if ( ! dnsConfig . token || typeof dnsConfig . token !== 'string' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'token must be a non-empty string' ) ) ;
if ( ! dnsConfig . email || typeof dnsConfig . email !== 'string' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'email must be a non-empty string' ) ) ;
2018-08-25 18:12:55 -07:00
if ( 'hyphenatedSubdomains' in dnsConfig && typeof dnsConfig . hyphenatedSubdomains !== 'boolean' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'hyphenatedSubdomains must be a boolean' ) ) ;
2017-04-26 11:06:33 +02:00
2017-04-26 08:45:20 +04:00
var credentials = {
2017-04-26 11:06:33 +02:00
token : dnsConfig . token ,
2018-08-22 12:16:19 +02:00
email : dnsConfig . email ,
hyphenatedSubdomains : ! ! dnsConfig . hyphenatedSubdomains
2017-04-26 08:45:20 +04:00
} ;
if ( process . env . BOX _ENV === 'test' ) return callback ( null , credentials ) ; // this shouldn't be here
2018-02-08 14:39:35 -08:00
dns . resolve ( zoneName , 'NS' , { timeout : 5000 } , function ( error , nameservers ) {
2018-04-29 11:20:12 -07:00
if ( error && error . code === 'ENOTFOUND' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'Unable to resolve nameservers for this domain' ) ) ;
if ( error || ! nameservers ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , error ? error . message : 'Unable to get nameservers' ) ) ;
2017-04-26 08:45:20 +04:00
2017-07-28 16:33:19 +02:00
getZoneByName ( dnsConfig , zoneName , function ( error , result ) {
2017-04-26 08:45:20 +04:00
if ( error ) return callback ( error ) ;
2017-07-28 16:41:00 +02:00
2017-04-26 08:45:20 +04:00
if ( ! _ . isEqual ( result . name _servers . sort ( ) , nameservers . sort ( ) ) ) {
debug ( 'verifyDnsConfig: %j and %j do not match' , nameservers , result . name _servers ) ;
2018-04-29 11:20:12 -07:00
return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'Domain nameservers are not set to Cloudflare' ) ) ;
2017-04-26 08:45:20 +04:00
}
2017-04-26 11:48:31 +02:00
2017-11-07 23:13:58 +01:00
const testSubdomain = 'cloudrontestdns' ;
upsert ( credentials , zoneName , testSubdomain , 'A' , [ ip ] , function ( error , changeId ) {
2017-04-26 08:45:20 +04:00
if ( error ) return callback ( error ) ;
2017-11-07 23:13:58 +01:00
debug ( 'verifyDnsConfig: Test A record added with change id %s' , changeId ) ;
del ( dnsConfig , zoneName , testSubdomain , 'A' , [ ip ] , function ( error ) {
if ( error ) return callback ( error ) ;
2017-04-26 08:45:20 +04:00
2017-11-07 23:13:58 +01:00
debug ( 'verifyDnsConfig: Test A record removed again' ) ;
callback ( null , credentials ) ;
} ) ;
2017-04-26 08:45:20 +04:00
} ) ;
} ) ;
} ) ;
}