2018-05-09 12:24:33 +02:00
'use strict' ;
exports = module . exports = {
upsert : upsert ,
get : get ,
del : del ,
waitForDns : require ( './waitfordns.js' ) ,
verifyDnsConfig : verifyDnsConfig
} ;
var assert = require ( 'assert' ) ,
debug = require ( 'debug' ) ( 'box:dns/namecom' ) ,
dns = require ( '../native-dns.js' ) ,
safe = require ( 'safetydance' ) ,
DomainsError = require ( '../domains.js' ) . DomainsError ,
superagent = require ( 'superagent' ) ;
const NAMECOM _API = 'https://api.name.com/v4' ;
function formatError ( response ) {
return ` Name.com DNS error [ ${ response . statusCode } ] ${ response . text } ` ;
}
function addRecord ( 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 ( Array . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( ` add: ${ subdomain } in zone ${ zoneName } of type ${ type } with values ${ JSON . stringify ( values ) } ` ) ;
var data = {
host : subdomain ,
type : type ,
ttl : 300 // 300 is the lowest
} ;
2018-05-24 09:38:01 -07:00
if ( type === 'MX' ) {
data . priority = parseInt ( values [ 0 ] . split ( ' ' ) [ 0 ] , 10 ) ;
data . answer = values [ 0 ] . split ( ' ' ) [ 1 ] ;
} else {
data . answer = values [ 0 ] ;
}
2018-05-09 12:24:33 +02:00
superagent . post ( ` ${ NAMECOM _API } /domains/ ${ zoneName } /records ` )
. auth ( dnsConfig . username , dnsConfig . token )
. timeout ( 30 * 1000 )
. send ( data )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , ` Network error ${ error . message } ` ) ) ;
if ( result . statusCode === 403 ) return callback ( new DomainsError ( DomainsError . ACCESS _DENIED , formatError ( result ) ) ) ;
if ( result . statusCode !== 200 ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , formatError ( result ) ) ) ;
2018-05-09 18:58:09 +02:00
return callback ( null , 'unused-id' ) ;
2018-05-09 12:24:33 +02:00
} ) ;
2018-05-24 09:38:01 -07:00
}
2018-05-09 12:24:33 +02:00
function updateRecord ( dnsConfig , zoneName , recordId , subdomain , type , values , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof recordId , 'number' ) ;
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert ( Array . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( ` update: ${ recordId } on ${ subdomain } in zone ${ zoneName } of type ${ type } with values ${ JSON . stringify ( values ) } ` ) ;
var data = {
host : subdomain ,
type : type ,
ttl : 300 // 300 is the lowest
} ;
2018-05-24 09:38:01 -07:00
if ( type === 'MX' ) {
data . priority = parseInt ( values [ 0 ] . split ( ' ' ) [ 0 ] , 10 ) ;
data . answer = values [ 0 ] . split ( ' ' ) [ 1 ] ;
} else {
data . answer = values [ 0 ] ;
}
2018-05-09 12:24:33 +02:00
superagent . put ( ` ${ NAMECOM _API } /domains/ ${ zoneName } /records/ ${ recordId } ` )
. auth ( dnsConfig . username , dnsConfig . token )
. timeout ( 30 * 1000 )
. send ( data )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , ` Network error ${ error . message } ` ) ) ;
if ( result . statusCode === 403 ) return callback ( new DomainsError ( DomainsError . ACCESS _DENIED , formatError ( result ) ) ) ;
if ( result . statusCode !== 200 ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , formatError ( result ) ) ) ;
2018-06-29 22:25:34 +02:00
return callback ( null ) ;
2018-05-09 12:24:33 +02:00
} ) ;
}
function getInternal ( 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' ) ;
subdomain = subdomain || '@' ;
debug ( ` getInternal: ${ subdomain } in zone ${ zoneName } of type ${ type } ` ) ;
superagent . get ( ` ${ NAMECOM _API } /domains/ ${ zoneName } /records ` )
. auth ( dnsConfig . username , dnsConfig . token )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , ` Network error ${ error . message } ` ) ) ;
if ( result . statusCode === 403 ) return callback ( new DomainsError ( DomainsError . ACCESS _DENIED , formatError ( result ) ) ) ;
if ( result . statusCode !== 200 ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , formatError ( result ) ) ) ;
// name.com does not return the correct content-type
result . body = safe . JSON . parse ( result . text ) ;
if ( ! result . body . records ) result . body . records = [ ] ;
result . body . records . forEach ( function ( r ) {
// name.com api simply strips empty properties
r . host = r . host || '@' ;
} ) ;
var results = result . body . records . filter ( function ( r ) {
return ( r . host === subdomain && r . type === type ) ;
} ) ;
debug ( 'getInternal: %j' , results ) ;
return callback ( null , results ) ;
} ) ;
}
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 ( Array . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
subdomain = subdomain || '@' ;
debug ( ` upsert: ${ subdomain } in zone ${ zoneName } of type ${ type } with values ${ JSON . stringify ( values ) } ` ) ;
getInternal ( dnsConfig , zoneName , subdomain , type , function ( error , result ) {
if ( error ) return callback ( error ) ;
if ( result . length === 0 ) return addRecord ( dnsConfig , zoneName , subdomain , type , values , callback ) ;
return updateRecord ( dnsConfig , zoneName , result [ 0 ] . id , subdomain , type , values , callback ) ;
} ) ;
}
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' ) ;
getInternal ( dnsConfig , zoneName , subdomain , 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 ) ;
} ) ;
}
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 ( Array . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
subdomain = subdomain || '@' ;
debug ( ` del: ${ subdomain } in zone ${ zoneName } of type ${ type } with values ${ JSON . stringify ( values ) } ` ) ;
getInternal ( dnsConfig , zoneName , subdomain , type , function ( error , result ) {
if ( error ) return callback ( error ) ;
if ( result . length === 0 ) return callback ( ) ;
superagent . del ( ` ${ NAMECOM _API } /domains/ ${ zoneName } /records/ ${ result [ 0 ] . id } ` )
. auth ( dnsConfig . username , dnsConfig . token )
. timeout ( 30 * 1000 )
. end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , ` Network error ${ error . message } ` ) ) ;
if ( result . statusCode === 403 ) return callback ( new DomainsError ( DomainsError . ACCESS _DENIED , formatError ( result ) ) ) ;
if ( result . statusCode !== 200 ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , formatError ( result ) ) ) ;
return callback ( null ) ;
} ) ;
} ) ;
}
function verifyDnsConfig ( dnsConfig , fqdn , zoneName , ip , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof fqdn , 'string' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-08-25 18:12:55 -07:00
if ( typeof dnsConfig . username !== 'string' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'username must be a string' ) ) ;
if ( typeof dnsConfig . token !== 'string' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'token must be a string' ) ) ;
if ( 'hyphenatedSubdomains' in dnsConfig && typeof dnsConfig . hyphenatedSubdomains !== 'boolean' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'hyphenatedSubdomains must be a boolean' ) ) ;
2018-05-09 12:24:33 +02:00
var credentials = {
username : dnsConfig . username ,
2018-08-22 12:16:19 +02:00
token : dnsConfig . token ,
hyphenatedSubdomains : ! ! dnsConfig . hyphenatedSubdomains
2018-05-09 12:24:33 +02:00
} ;
if ( process . env . BOX _ENV === 'test' ) return callback ( null , credentials ) ; // this shouldn't be here
dns . resolve ( zoneName , 'NS' , { timeout : 5000 } , function ( error , nameservers ) {
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' ) ) ;
if ( ! nameservers . every ( function ( n ) { return n . toLowerCase ( ) . indexOf ( '.name.com' ) !== - 1 ; } ) ) {
debug ( 'verifyDnsConfig: %j does not contain Name.com NS' , nameservers ) ;
return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'Domain nameservers are not set to Name.com' ) ) ;
}
const testSubdomain = 'cloudrontestdns' ;
upsert ( credentials , zoneName , testSubdomain , 'A' , [ ip ] , function ( error , changeId ) {
if ( error ) return callback ( error ) ;
debug ( 'verifyDnsConfig: Test A record added with change id %s' , changeId ) ;
del ( dnsConfig , zoneName , testSubdomain , 'A' , [ ip ] , function ( error ) {
if ( error ) return callback ( error ) ;
debug ( 'verifyDnsConfig: Test A record removed again' ) ;
callback ( null , credentials ) ;
} ) ;
} ) ;
} ) ;
}