2024-11-26 13:11:26 +05:30
'use strict' ;
2025-10-08 20:11:55 +02:00
exports = module . exports = {
removePrivateFields ,
injectPrivateFields ,
upsert ,
get ,
del ,
wait ,
verifyDomainConfig
} ;
2024-11-26 13:11:26 +05:30
const { ApiClient , Language } = require ( 'domrobot-client' ) ,
2025-08-14 11:17:38 +05:30
assert = require ( 'node:assert' ) ,
2024-11-26 13:11:26 +05:30
BoxError = require ( '../boxerror.js' ) ,
constants = require ( '../constants.js' ) ,
debug = require ( 'debug' ) ( 'box:dns/inwx' ) ,
dig = require ( '../dig.js' ) ,
dns = require ( '../dns.js' ) ,
safe = require ( 'safetydance' ) ,
waitForDns = require ( './waitfordns.js' ) ;
function formatError ( response ) {
return ` INWX Api error error [Code: [ ${ response . code } ] Message: ${ response . msg } ` ;
}
function removePrivateFields ( domainObject ) {
2025-10-08 12:04:31 +02:00
delete domainObject . config . password ;
2024-11-26 13:11:26 +05:30
return domainObject ;
}
function injectPrivateFields ( newConfig , currentConfig ) {
2025-10-08 12:04:31 +02:00
if ( ! Object . hasOwn ( newConfig , 'password' ) ) newConfig . password = currentConfig . password ;
2024-11-26 13:11:26 +05:30
}
// https://www.inwx.com/en/help/apidoc/f/ch04.html
function translateError ( response ) {
if ( response . code === 2200 || response . code === 2201 || response . code === 2202 ) throw new BoxError ( BoxError . ACCESS _DENIED , formatError ( response ) ) ;
if ( response . code === 2003 || response . code === 2004 || response . code === 2005 ) throw new BoxError ( BoxError . BAD _FIELD , formatError ( response ) ) ;
if ( response . code !== 1000 ) throw new BoxError ( BoxError . EXTERNAL _ERROR , formatError ( response ) ) ;
}
async function login ( domainConfig ) {
const apiClient = new ApiClient ( ApiClient . API _URL _LIVE , Language . EN , false /* debug mode */ ) ;
const sharedSecret = '' ; // 2FA
const [ error , response ] = await safe ( apiClient . login ( domainConfig . username , domainConfig . password , sharedSecret ) ) ;
if ( error ) throw new BoxError ( BoxError . NETWORK _ERROR , error ) ;
if ( response . code !== 1000 ) throw new BoxError ( BoxError . ACCESS _DENIED , ` Api login error. Code: ${ response . code } Message: ${ response . msg } ` ) ;
return apiClient ;
}
async function getDnsRecords ( domainConfig , apiClient , zoneName , fqdn , type ) {
assert . strictEqual ( typeof domainConfig , 'object' ) ;
assert . strictEqual ( typeof apiClient , 'object' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof fqdn , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
debug ( ` getDnsRecords: ${ fqdn } in zone ${ zoneName } of type ${ type } ` ) ;
const [ error , response ] = await safe ( apiClient . callApi ( 'nameserver.info' , { domain : zoneName , name : fqdn , type } ) ) ;
if ( error ) throw new BoxError ( BoxError . NETWORK _ERROR , error ) ;
if ( response . code !== 1000 ) throw translateError ( response ) ;
return response . resData . record || [ ] ; // 'record' property will be missing if no records
}
async function upsert ( domainObject , location , type , values ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert ( Array . isArray ( values ) ) ;
const domainConfig = domainObject . config ,
zoneName = domainObject . zoneName ;
debug ( ` upsert: ${ location } in zone ${ zoneName } of type ${ type } with values ${ JSON . stringify ( values ) } ` ) ;
const apiClient = await login ( domainConfig ) ;
const fqdn = dns . fqdn ( location , domainObject . domain ) ;
const records = await getDnsRecords ( domainConfig , apiClient , zoneName , fqdn , type ) ;
let i = 0 ; // // used to track available records to update instead of create
for ( let value of values ) {
let priority = 0 ;
if ( type === 'MX' ) {
priority = parseInt ( value . split ( ' ' ) [ 0 ] , 10 ) ;
value = value . split ( ' ' ) [ 1 ] ;
}
if ( i >= records . length ) { // create a new record
const data = {
type ,
name : fqdn ,
domain : zoneName ,
content : value ,
prio : priority ,
ttl : 300 // 300 to 2764800
} ;
const [ error , response ] = await safe ( apiClient . callApi ( 'nameserver.createRecord' , data ) ) ;
if ( error ) throw new BoxError ( BoxError . NETWORK _ERROR , error ) ;
if ( response . code !== 1000 ) throw translateError ( response ) ;
} else { // replace existing record
const data = {
id : records [ i ] . id ,
type ,
name : fqdn ,
content : value ,
} ;
const [ error , response ] = await safe ( apiClient . callApi ( 'nameserver.updateRecord' , data ) ) ;
if ( error ) throw new BoxError ( BoxError . NETWORK _ERROR , error ) ;
if ( response . code !== 1000 ) throw translateError ( response ) ;
++ i ; // increment, as we have consumed the record
}
}
for ( let j = values . length + 1 ; j < records . length ; j ++ ) {
const [ error , response ] = await safe ( apiClient . callApi ( 'nameserver.deleteRecord' , { id : records [ j ] . id } ) ) ;
if ( error ) throw new BoxError ( BoxError . NETWORK _ERROR , error ) ;
if ( response . code !== 1000 ) throw translateError ( response ) ;
}
}
async function get ( domainObject , location , type ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
const domainConfig = domainObject . config ,
zoneName = domainObject . zoneName ;
const apiClient = await login ( domainConfig ) ;
const fqdn = dns . fqdn ( location , domainObject . domain ) ;
const result = await getDnsRecords ( domainConfig , apiClient , zoneName , fqdn , type ) ;
const tmp = result . map ( function ( record ) { return record . content ; } ) ;
return tmp ;
}
async function del ( domainObject , location , type , values ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
assert . strictEqual ( typeof location , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
assert ( Array . isArray ( values ) ) ;
const domainConfig = domainObject . config ,
zoneName = domainObject . zoneName ;
debug ( ` del: ${ location } in zone ${ zoneName } of type ${ type } with values ${ JSON . stringify ( values ) } ` ) ;
const apiClient = await login ( domainConfig ) ;
const fqdn = dns . fqdn ( location , domainObject . domain ) ;
const result = await getDnsRecords ( domainConfig , apiClient , zoneName , fqdn , type ) ;
if ( result . length === 0 ) return ;
const tmp = result . filter ( function ( record ) { return values . some ( function ( value ) { return value === record . content ; } ) ; } ) ;
debug ( 'del: %j' , tmp ) ;
for ( const r of tmp ) {
const [ error , response ] = await safe ( apiClient . callApi ( 'nameserver.deleteRecord' , { id : r . id } ) ) ;
if ( error ) throw new BoxError ( BoxError . NETWORK _ERROR , error ) ;
if ( response . code !== 1000 ) throw translateError ( response ) ;
}
}
async function wait ( domainObject , subdomain , type , value , options ) {
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 }
const fqdn = dns . fqdn ( subdomain , domainObject . domain ) ;
await waitForDns ( fqdn , domainObject . zoneName , type , value , options ) ;
}
async function verifyDomainConfig ( domainObject ) {
assert . strictEqual ( typeof domainObject , 'object' ) ;
const domainConfig = domainObject . config ,
zoneName = domainObject . zoneName ;
if ( typeof domainConfig . username !== 'string' ) throw new BoxError ( BoxError . BAD _FIELD , 'username must be a string' ) ;
if ( typeof domainConfig . password !== 'string' ) throw new BoxError ( BoxError . BAD _FIELD , 'password must be a string' ) ;
2025-03-02 07:27:09 +01:00
if ( 'customNameservers' in domainConfig && typeof domainConfig . customNameservers !== 'boolean' ) throw new BoxError ( BoxError . BAD _FIELD , 'customNameservers must be a boolean' ) ;
2024-11-26 13:11:26 +05:30
const credentials = {
username : domainConfig . username ,
2025-03-02 07:27:09 +01:00
password : domainConfig . password ,
customNameservers : ! ! domainConfig . customNameservers
2024-11-26 13:11:26 +05:30
} ;
const ip = '127.0.0.1' ;
if ( constants . TEST ) return credentials ; // this shouldn't be here
const [ error , nameservers ] = await safe ( dig . resolve ( zoneName , 'NS' , { timeout : 5000 } ) ) ;
if ( error && error . code === 'ENOTFOUND' ) throw new BoxError ( BoxError . BAD _FIELD , 'Unable to resolve nameservers for this domain' ) ;
if ( error || ! nameservers ) throw new BoxError ( BoxError . BAD _FIELD , error ? error . message : 'Unable to get nameservers' ) ;
if ( ! nameservers . every ( function ( n ) { return n . toLowerCase ( ) . search ( /inwx|xnameserver|domrobot/ ) !== - 1 ; } ) ) {
debug ( 'verifyDomainConfig: %j does not contain INWX NS' , nameservers ) ;
2025-03-02 07:27:09 +01:00
if ( ! domainConfig . customNameservers ) throw new BoxError ( BoxError . BAD _FIELD , 'Domain nameservers are not set to INWX' ) ;
2024-11-26 13:11:26 +05:30
}
const location = 'cloudrontestdns' ;
await upsert ( domainObject , location , 'A' , [ ip ] ) ;
debug ( 'verifyDomainConfig: Test A record added' ) ;
await del ( domainObject , location , 'A' , [ ip ] ) ;
debug ( 'verifyDomainConfig: Test A record removed again' ) ;
return credentials ;
}
2025-10-08 12:04:31 +02:00