2019-01-16 18:05:42 +02:00
'use strict' ;
exports = module . exports = {
upsert : upsert ,
get : get ,
del : del ,
2019-01-22 12:12:46 +01:00
verifyDnsConfig : verifyDnsConfig ,
wait : wait
2019-01-16 18:05:42 +02:00
} ;
var assert = require ( 'assert' ) ,
debug = require ( 'debug' ) ( 'box:dns/namecheap' ) ,
dns = require ( '../native-dns.js' ) ,
2019-01-21 14:36:21 +01:00
domains = require ( '../domains.js' ) ,
2019-01-16 18:05:42 +02:00
DomainsError = require ( '../domains.js' ) . DomainsError ,
Namecheap = require ( 'namecheap' ) ,
sysinfo = require ( '../sysinfo.js' ) ,
2019-01-22 12:12:46 +01:00
util = require ( 'util' ) ,
waitForDns = require ( './waitfordns.js' ) ;
2019-01-16 18:05:42 +02:00
function formatError ( response ) {
return util . format ( 'NameCheap DNS error [%s] %j' , response . code , response . message ) ;
}
2019-01-24 14:35:15 +01:00
// Only send required fields - https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
2019-01-16 18:05:42 +02:00
function mapHosts ( hosts ) {
2019-01-24 14:35:15 +01:00
return hosts . map ( function ( host ) {
let tmp = { } ;
tmp . TTL = '300' ;
tmp . RecordType = host . RecordType || host . Type ;
tmp . HostName = host . HostName || host . Name ;
tmp . Address = host . Address ;
2019-01-16 18:05:42 +02:00
2019-01-24 14:35:15 +01:00
if ( tmp . RecordType === 'MX' ) {
tmp . EmailType = 'MX' ;
if ( host . MXPref ) tmp . MXPref = host . MXPref ;
2019-01-16 18:05:42 +02:00
}
2019-01-24 14:35:15 +01:00
return tmp ;
} ) ;
2019-01-16 18:05:42 +02:00
}
2019-02-08 20:21:16 -08:00
function getApi ( dnsConfig , callback ) {
2019-01-21 14:36:21 +01:00
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
sysinfo . getPublicIp ( function ( error , ip ) {
if ( error ) return callback ( new DomainsError ( DomainsError . INTERNAL _ERROR , error ) ) ;
// Note that for all NameCheap calls to go through properly, the public IP returned by the getPublicIp method below must be whitelisted on NameCheap's API dashboard
2019-02-08 20:21:16 -08:00
let namecheap = new Namecheap ( dnsConfig . username , dnsConfig . apiKey , ip ) ;
2019-01-21 14:36:21 +01:00
namecheap . setUsername ( dnsConfig . username ) ;
2019-02-08 20:21:16 -08:00
callback ( null , namecheap ) ;
2019-01-21 14:36:21 +01:00
} ) ;
}
2019-01-16 18:05:42 +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' ) ;
2019-02-08 20:21:16 -08:00
getApi ( dnsConfig , function ( error , namecheap ) {
2019-01-21 14:36:21 +01:00
if ( error ) return callback ( error ) ;
2019-01-16 18:05:42 +02:00
2019-01-21 14:36:21 +01:00
namecheap . domains . dns . getHosts ( zoneName , function ( error , result ) {
if ( error ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , formatError ( error ) ) ) ;
2019-01-16 18:05:42 +02:00
2019-01-24 14:13:36 +01:00
debug ( 'entire getInternal response: %j' , result ) ;
2019-01-16 18:05:42 +02:00
2019-01-21 14:36:21 +01:00
return callback ( null , result [ 'DomainDNSGetHostsResult' ] [ 'host' ] ) ;
} ) ;
2019-01-16 18:05:42 +02:00
} ) ;
}
2019-02-08 20:21:16 -08:00
function setInternal ( dnsConfig , zoneName , hosts , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert ( Array . isArray ( hosts ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-16 18:05:42 +02:00
let mappedHosts = mapHosts ( hosts ) ;
2019-02-08 20:21:16 -08:00
getApi ( dnsConfig , function ( error , namecheap ) {
if ( error ) return callback ( error ) ;
namecheap . domains . dns . setHosts ( zoneName , mappedHosts , function ( error , result ) {
if ( error ) return callback ( new DomainsError ( DomainsError . EXTERNAL _ERROR , formatError ( error ) ) ) ;
return callback ( null , result ) ;
} ) ;
2019-01-16 18:05:42 +02:00
} ) ;
}
2019-01-21 14:36:21 +01:00
function upsert ( domainObject , subdomain , type , values , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
2019-01-16 18:05:42 +02:00
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert ( util . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-21 14:36:21 +01:00
const dnsConfig = domainObject . config ;
const zoneName = domainObject . zoneName ;
subdomain = domains . getName ( domainObject , subdomain , type ) || '@' ;
2019-01-16 18:05:42 +02:00
debug ( 'upsert: %s for zone %s of type %s with values %j' , subdomain , zoneName , type , values ) ;
getInternal ( dnsConfig , zoneName , subdomain , type , function ( error , result ) {
2019-01-21 14:36:21 +01:00
if ( error ) return callback ( error ) ;
2019-01-16 18:05:42 +02:00
// Array to keep track of records that need to be inserted
let toInsert = [ ] ;
for ( var i = 0 ; i < values . length ; i ++ ) {
let curValue = values [ i ] ;
let wasUpdate = false ;
for ( var j = 0 ; j < result . length ; j ++ ) {
let curHost = result [ j ] ;
if ( curHost . Type === type && curHost . Name === subdomain ) {
// Updating an already existing host
wasUpdate = true ;
2019-01-21 14:36:21 +01:00
if ( type === 'MX' ) {
2019-01-16 18:05:42 +02:00
curHost . MXPref = curValue . split ( ' ' ) [ 0 ] ;
curHost . Address = curValue . split ( ' ' ) [ 1 ] ;
} else {
curHost . Address = curValue ;
}
}
}
// We don't have this host at all yet, let's push to toInsert array
if ( ! wasUpdate ) {
let newRecord = {
RecordType : type ,
HostName : subdomain ,
Address : curValue
} ;
// Special case for MX records
2019-01-21 14:36:21 +01:00
if ( type === 'MX' ) {
2019-01-16 18:05:42 +02:00
newRecord . MXPref = curValue . split ( ' ' ) [ 0 ] ;
newRecord . Address = curValue . split ( ' ' ) [ 1 ] ;
}
toInsert . push ( newRecord ) ;
}
}
let toUpsert = result . concat ( toInsert ) ;
2019-02-08 20:21:16 -08:00
setInternal ( dnsConfig , zoneName , toUpsert , callback ) ;
2019-01-16 18:05:42 +02:00
} ) ;
}
2019-01-21 14:36:21 +01:00
function get ( domainObject , subdomain , type , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
2019-01-16 18:05:42 +02:00
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-21 14:36:21 +01:00
const dnsConfig = domainObject . config ;
const zoneName = domainObject . zoneName ;
subdomain = domains . getName ( domainObject , subdomain , type ) || '@' ;
2019-01-16 18:05:42 +02:00
getInternal ( dnsConfig , zoneName , subdomain , type , function ( error , result ) {
if ( error ) return callback ( error ) ;
// We need to filter hosts to ones with this subdomain and type
let actualHosts = result . filter ( ( host ) => host . Type === type && host . Name === subdomain ) ;
2019-01-21 14:36:21 +01:00
2019-01-16 18:05:42 +02:00
// We only return the value string
var tmp = actualHosts . map ( function ( record ) { return record . Address ; } ) ;
debug ( 'get: %j' , tmp ) ;
return callback ( null , tmp ) ;
} ) ;
}
2019-01-21 14:36:21 +01:00
function del ( domainObject , subdomain , type , values , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
2019-01-16 18:05:42 +02:00
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert ( util . isArray ( values ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-21 14:36:21 +01:00
const dnsConfig = domainObject . config ;
const zoneName = domainObject . zoneName ;
subdomain = domains . getName ( domainObject , subdomain , type ) || '@' ;
2019-01-16 18:05:42 +02:00
debug ( 'del: %s for zone %s of type %s with values %j' , subdomain , zoneName , type , values ) ;
getInternal ( dnsConfig , zoneName , subdomain , type , function ( error , result ) {
if ( error ) return callback ( error ) ;
2019-01-21 14:36:21 +01:00
if ( result . length === 0 ) return callback ( ) ;
2019-01-16 18:05:42 +02:00
let removed = false ;
for ( var i = 0 ; i < values . length ; i ++ ) {
let curValue = values [ i ] ;
for ( var j = 0 ; j < result . length ; j ++ ) {
let curHost = result [ i ] ;
if ( curHost . Type === type && curHost . Name === subdomain && curHost . Address === curValue ) {
removed = true ;
2019-01-21 14:36:21 +01:00
2019-01-16 18:05:42 +02:00
result . splice ( i , 1 ) ; // Remove element from result array
}
}
}
2019-01-21 14:36:21 +01:00
// Only set hosts if we actually removed a host
2019-02-08 20:21:16 -08:00
if ( removed ) return setInternal ( dnsConfig , zoneName , result , callback ) ;
2019-01-21 14:36:21 +01:00
callback ( ) ;
2019-01-16 18:05:42 +02:00
} ) ;
}
2019-01-21 14:36:21 +01:00
function verifyDnsConfig ( domainObject , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
2019-01-16 18:05:42 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-21 14:36:21 +01:00
const dnsConfig = domainObject . config ;
const zoneName = domainObject . zoneName ;
const ip = '127.0.0.1' ;
2019-01-16 18:05:42 +02:00
if ( ! dnsConfig . username || typeof dnsConfig . username !== 'string' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'username must be a non-empty string' ) ) ;
if ( ! dnsConfig . apiKey || typeof dnsConfig . apiKey !== 'string' ) return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'apiKey must be a non-empty string' ) ) ;
2019-02-08 19:09:28 -08:00
let credentials = {
2019-01-16 18:05:42 +02:00
username : dnsConfig . username ,
2019-02-08 19:09:28 -08:00
apiKey : dnsConfig . apiKey
2019-01-16 18:05:42 +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' ) ) ;
2019-01-22 11:56:56 +01:00
if ( nameservers . some ( function ( n ) { return n . toLowerCase ( ) . indexOf ( '.registrar-servers.com' ) === - 1 ; } ) ) {
2019-01-16 18:05:42 +02:00
debug ( 'verifyDnsConfig: %j does not contains NC NS' , nameservers ) ;
return callback ( new DomainsError ( DomainsError . BAD _FIELD , 'Domain nameservers are not set to NameCheap' ) ) ;
}
const testSubdomain = 'cloudrontestdns' ;
2019-01-21 14:36:21 +01:00
upsert ( domainObject , testSubdomain , 'A' , [ ip ] , function ( error , changeId ) {
2019-01-16 18:05:42 +02:00
if ( error ) return callback ( error ) ;
debug ( 'verifyDnsConfig: Test A record added with change id %s' , changeId ) ;
2019-01-22 12:04:17 +01:00
del ( domainObject , testSubdomain , 'A' , [ ip ] , function ( error ) {
2019-01-16 18:05:42 +02:00
if ( error ) return callback ( error ) ;
debug ( 'verifyDnsConfig: Test A record removed again' ) ;
2019-02-08 19:09:28 -08:00
callback ( null , credentials ) ;
2019-01-16 18:05:42 +02:00
} ) ;
} ) ;
} ) ;
}
2019-01-22 12:12:46 +01:00
function wait ( domainObject , subdomain , type , value , options , callback ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert . strictEqual ( typeof value , 'string' ) ;
assert ( options && typeof options === 'object' ) ; // { interval: 5000, times: 50000 }
assert . strictEqual ( typeof callback , 'function' ) ;
const fqdn = domains . fqdn ( subdomain , domainObject ) ;
waitForDns ( fqdn , domainObject . zoneName , type , value , options , callback ) ;
}