From cfcc1acb8c30dd77974f8d80559a9933a74b23ea Mon Sep 17 00:00:00 2001 From: tigerbot Date: Tue, 19 Sep 2017 13:18:22 -0600 Subject: [PATCH] updated the DDNS and loopback to use async/await --- lib/ddns.js | 231 ++++++++++++++++++++++-------------------------- lib/loopback.js | 110 +++++++++++------------ 2 files changed, 161 insertions(+), 180 deletions(-) diff --git a/lib/ddns.js b/lib/ddns.js index a2bf08b..6985e2a 100644 --- a/lib/ddns.js +++ b/lib/ddns.js @@ -16,145 +16,126 @@ module.exports.create = function (deps, conf) { } } - function setDeviceAddress(addr) { - return deps.storage.owners.all().then(function (sessions) { - return sessions.filter(function (sess) { - return sess.token.scp.indexOf('dns') >= 0; - })[0]; - }).then(function (session) { - if (!session) { - return PromiseA.reject(new Error('no sessions with DNS grants')); - } + async function getSession() { + var sessions = await deps.storage.owners.all(); + var session = sessions.filter(function (sess) { + return sess.token.scp.indexOf('dns') >= 0; + })[0]; - // The OAUTH3 library stores some things on the root session object that we usually - // just leave inside the token, but we need to pull those out before we use it here - session.provider_uri = session.provider_uri || session.token.provider_uri || session.token.iss; - session.client_uri = session.client_uri || session.token.azp; - session.scope = session.scope || session.token.scp; + if (!session) { + throw new Error('no sessions with DNS grants'); + } - return OAUTH3.discover(session.token.aud).then(function (directives) { - return request({ - url: directives.api+'/api/com.daplie.domains/acl/devices/' + conf.device.hostname - , method: 'POST' - , headers: { - 'Authorization': 'Bearer ' + session.refresh_token - , 'Accept': 'application/json; charset=utf-8' - } - , json: { - addresses: [ - { value: addr, type: dnsType(addr) } - ] - } - }).then(function () { - return OAUTH3.api(directives.api, {session: session, api: 'dns.list'}).then(function (list) { - return list.filter(function (record) { - return record.device === conf.device.hostname; - }).map(function (record) { - var split = record.zone.split('.'); - return { - tld: split.slice(1).join('.') - , sld: split[0] - , sub: record.host.slice(0, -(record.zone.length + 1)) - }; - }); - }); - }).then(function (domains) { - var common = { - api: 'devices.detach' - , session: session - , device: conf.device.hostname - }; - - return PromiseA.all(domains.map(function (record) { - return OAUTH3.api(directives.api, Object.assign({}, common, record)); - })).then(function () { - return domains; - }); - }).then(function (domains) { - var common = { - api: 'devices.attach' - , session: session - , device: conf.device.hostname - , ip: addr - , ttl: 300 - }; - - return PromiseA.all(domains.map(function (record) { - return OAUTH3.api(directives.api, Object.assign({}, common, record)); - })); - }); - }); - }); + // The OAUTH3 library stores some things on the root session object that we usually + // just leave inside the token, but we need to pull those out before we use it here + session.provider_uri = session.provider_uri || session.token.provider_uri || session.token.iss; + session.client_uri = session.client_uri || session.token.azp; + session.scope = session.scope || session.token.scp; + return session; } - function getDeviceAddresses() { - return deps.storage.owners.all().then(function (sessions) { - return sessions.filter(function (sess) { - return sess.token.scp.indexOf('dns') >= 0; - })[0]; - }).then(function (session) { - if (!session) { - return PromiseA.reject(new Error('no sessions with DNS grants')); - } + async function setDeviceAddress(addr) { + var session = await getSession(); + var directives = await OAUTH3.discover(session.token.aud); - return OAUTH3.discover(session.token.aud).then(function (directives) { - return request({ - url: directives.api+'/api/org.oauth3.dns/acl/devices' - , method: 'GET' - , headers: { - 'Authorization': 'Bearer ' + session.refresh_token - , 'Accept': 'application/json; charset=utf-8' - } - , json: true - }); - }).then(function (result) { - if (!result.body) { - return PromiseA.reject(new Error('No response body in request for device addresses')); - } - if (result.body.error) { - var err = new Error(result.body.error.message); - return PromiseA.reject(Object.assign(err, result.body.error)); - } - return result.body.devices.filter(function (dev) { - return dev.name === conf.device.hostname; - })[0]; - }).then(function (dev) { - return (dev || {}).addresses || []; - }); + // Set the address of the device to our public address. + await request({ + url: directives.api+'/api/com.daplie.domains/acl/devices/' + conf.device.hostname + , method: 'POST' + , headers: { + 'Authorization': 'Bearer ' + session.refresh_token + , 'Accept': 'application/json; charset=utf-8' + } + , json: { + addresses: [ + { value: addr, type: dnsType(addr) } + ] + } }); + + // Then update all of the records attached to our hostname, first removing the old records + // to remove the reference to the old address, then creating new records for the same domains + // using our new address. + var allDns = OAUTH3.api(directives.api, {session: session, api: 'dns.list'}); + var ourDomains = allDns.filter(function (record) { + return record.device === conf.device.hostname; + }).map(function (record) { + var zoneSplit = record.zone.split('.'); + return { + tld: zoneSplit.slice(1).join('.') + , sld: zoneSplit[0] + , sub: record.host.slice(0, -(record.zone.length + 1)) + }; + }); + + var common = { + api: 'devices.detach' + , session: session + , device: conf.device.hostname + }; + await PromiseA.all(ourDomains.map(function (record) { + return OAUTH3.api(directives.api, Object.assign({}, common, record)); + })); + + common = { + api: 'devices.attach' + , session: session + , device: conf.device.hostname + , ip: addr + , ttl: 300 + }; + await PromiseA.all(ourDomains.map(function (record) { + return OAUTH3.api(directives.api, Object.assign({}, common, record)); + })); + } + + async function getDeviceAddresses() { + var session = await getSession(); + var directives = await OAUTH3.discover(session.token.aud); + + var result = await request({ + url: directives.api+'/api/org.oauth3.dns/acl/devices' + , method: 'GET' + , headers: { + 'Authorization': 'Bearer ' + session.refresh_token + , 'Accept': 'application/json; charset=utf-8' + } + , json: true + }); + + if (!result.body) { + throw new Error('No response body in request for device addresses'); + } + if (result.body.error) { + throw Object.assign(new Error('error getting device list'), result.body.error); + } + + var dev = result.body.devices.filter(function (dev) { + return dev.name === conf.device.hostname; + })[0]; + return (dev || {}).addresses || []; } var publicAddress; - function recheckPubAddr() { + async function recheckPubAddr() { if (!conf.ddns.enabled) { return; } - deps.storage.owners.all().then(function (sessions) { - return sessions.filter(function (sess) { - return sess.token.scp.indexOf('dns') >= 0; - })[0]; - }).then(function (session) { - if (!session) { - return; - } + var session = await getSession(); + var directives = await OAUTH3.discover(session.token.aud); + var addr = await deps.loopback.checkPublicAddr(directives.api); - OAUTH3.discover(session.token.aud).then(function (directives) { - return deps.loopback.checkPublicAddr(directives.api); - }).then(function (addr) { - if (publicAddress !== addr) { - if (conf.debug) { - console.log('previous public address',publicAddress, 'does not match current public address', addr); - } - publicAddress = addr; - setDeviceAddress(addr); - } - }, function (err) { - if (conf.debug) { - console.error('error getting public address', err); - } - }); - }); + if (publicAddress === addr) { + return; + } + + if (conf.debug) { + console.log('previous public address',publicAddress, 'does not match current public address', addr); + } + + await setDeviceAddress(addr); + publicAddress = addr; } recheckPubAddr(); diff --git a/lib/loopback.js b/lib/loopback.js index 906aae7..6b4c389 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -5,81 +5,81 @@ module.exports.create = function (deps, conf) { var request = PromiseA.promisify(require('request')); var pending = {}; - function checkPublicAddr(host) { - return request({ + async function checkPublicAddr(host) { + var result = await request({ method: 'GET' , url: host+'/api/org.oauth3.tunnel/checkip' , json: true - }).then(function (result) { - if (!result.body) { - return PromiseA.reject(new Error('No response body in request for public address')); - } - if (result.body.error) { - var err = new Error(result.body.error.message); - return PromiseA.reject(Object.assign(err, result.body.error)); - } - return result.body.address; }); + + if (!result.body) { + throw new Error('No response body in request for public address'); + } + if (result.body.error) { + // Note that the error on the body will probably have a message that overwrites the default + throw Object.assign(new Error('error in check IP response'), result.body.error); + } + return result.body.address; } - function checkSinglePort(host, address, port) { + async function checkSinglePort(host, address, port) { var crypto = require('crypto'); var token = crypto.randomBytes(8).toString('hex'); var keyAuth = crypto.randomBytes(32).toString('hex'); pending[token] = keyAuth; - var opts = { - address: address - , port: port - , token: token - , keyAuthorization: keyAuth - , iat: Date.now() - }; - - return request({ + var reqObj = { method: 'POST' , url: host+'/api/org.oauth3.tunnel/loopback' - , json: opts - }) - .then(function (result) { - delete pending[token]; - if (!result.body) { - return PromiseA.reject(new Error('No response body in loopback request for port '+port)); + , json: { + address: address + , port: port + , token: token + , keyAuthorization: keyAuth + , iat: Date.now() } - // If the loopback requests don't go to us then there are all kinds of ways it could - // error, but none of them really provide much extra information so we don't do - // anything that will break the PromiseA.all out and mask the other results. - if (conf.debug && result.body.error) { - console.log('error on remote side of port '+port+' loopback', result.body.error); - } - return !!result.body.success; - }, function (err) { + }; + + var result; + try { + result = await request(reqObj); + } catch (err) { delete pending[token]; throw err; - }); + } + + delete pending[token]; + if (!result.body) { + throw new Error('No response body in loopback request for port '+port); + } + // If the loopback requests don't go to us then there are all kinds of ways it could + // error, but none of them really provide much extra information so we don't do + // anything that will break the PromiseA.all out and mask the other results. + if (conf.debug && result.body.error) { + console.log('error on remote side of port '+port+' loopback', result.body.error); + } + return !!result.body.success; } - function loopback(provider) { - return deps.OAUTH3.discover(provider).then(function (directives) { - return checkPublicAddr(directives.api).then(function (address) { - console.log('checking to see if', address, 'gets back to us'); - var ports = require('./servers').listeners.tcp.list(); - return PromiseA.all(ports.map(function (port) { - return checkSinglePort(directives.api, address, port); - })) - .then(function (values) { - if (conf.debug) { - console.log('remaining loopback tokens', pending); - } + async function loopback(provider) { + var directives = await deps.OAUTH3.discover(provider); + var address = await checkPublicAddr(directives.api); + console.log('checking to see if', address, 'gets back to us'); - var result = {error: null, address: address}; - ports.forEach(function (port, ind) { - result[port] = values[ind]; - }); - return result; - }); - }); + var ports = require('./servers').listeners.tcp.list(); + var values = await PromiseA.all(ports.map(function (port) { + return checkSinglePort(directives.api, address, port); + })); + + if (conf.debug) { + console.log('remaining loopback tokens', pending); + } + + var result = {error: null, address: address}; + ports.forEach(function (port, ind) { + result[port] = values[ind]; }); + return result; } loopback.checkPublicAddr = checkPublicAddr;