updated the DDNS and loopback to use async/await

This commit is contained in:
tigerbot 2017-09-19 13:18:22 -06:00
parent a625ee9db9
commit cfcc1acb8c
2 changed files with 161 additions and 180 deletions

View File

@ -16,145 +16,126 @@ module.exports.create = function (deps, conf) {
} }
} }
function setDeviceAddress(addr) { async function getSession() {
return deps.storage.owners.all().then(function (sessions) { var sessions = await deps.storage.owners.all();
return sessions.filter(function (sess) { var session = sessions.filter(function (sess) {
return sess.token.scp.indexOf('dns') >= 0; return sess.token.scp.indexOf('dns') >= 0;
})[0]; })[0];
}).then(function (session) {
if (!session) {
return PromiseA.reject(new Error('no sessions with DNS grants'));
}
// The OAUTH3 library stores some things on the root session object that we usually if (!session) {
// just leave inside the token, but we need to pull those out before we use it here throw new Error('no sessions with DNS grants');
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 OAUTH3.discover(session.token.aud).then(function (directives) { // The OAUTH3 library stores some things on the root session object that we usually
return request({ // just leave inside the token, but we need to pull those out before we use it here
url: directives.api+'/api/com.daplie.domains/acl/devices/' + conf.device.hostname session.provider_uri = session.provider_uri || session.token.provider_uri || session.token.iss;
, method: 'POST' session.client_uri = session.client_uri || session.token.azp;
, headers: { session.scope = session.scope || session.token.scp;
'Authorization': 'Bearer ' + session.refresh_token return session;
, '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));
}));
});
});
});
} }
function getDeviceAddresses() { async function setDeviceAddress(addr) {
return deps.storage.owners.all().then(function (sessions) { var session = await getSession();
return sessions.filter(function (sess) { var directives = await OAUTH3.discover(session.token.aud);
return sess.token.scp.indexOf('dns') >= 0;
})[0];
}).then(function (session) {
if (!session) {
return PromiseA.reject(new Error('no sessions with DNS grants'));
}
return OAUTH3.discover(session.token.aud).then(function (directives) { // Set the address of the device to our public address.
return request({ await request({
url: directives.api+'/api/org.oauth3.dns/acl/devices' url: directives.api+'/api/com.daplie.domains/acl/devices/' + conf.device.hostname
, method: 'GET' , method: 'POST'
, headers: { , headers: {
'Authorization': 'Bearer ' + session.refresh_token 'Authorization': 'Bearer ' + session.refresh_token
, 'Accept': 'application/json; charset=utf-8' , 'Accept': 'application/json; charset=utf-8'
} }
, json: true , json: {
}); addresses: [
}).then(function (result) { { value: addr, type: dnsType(addr) }
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 || [];
});
}); });
// 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; var publicAddress;
function recheckPubAddr() { async function recheckPubAddr() {
if (!conf.ddns.enabled) { if (!conf.ddns.enabled) {
return; return;
} }
deps.storage.owners.all().then(function (sessions) { var session = await getSession();
return sessions.filter(function (sess) { var directives = await OAUTH3.discover(session.token.aud);
return sess.token.scp.indexOf('dns') >= 0; var addr = await deps.loopback.checkPublicAddr(directives.api);
})[0];
}).then(function (session) {
if (!session) {
return;
}
OAUTH3.discover(session.token.aud).then(function (directives) { if (publicAddress === addr) {
return deps.loopback.checkPublicAddr(directives.api); return;
}).then(function (addr) { }
if (publicAddress !== addr) {
if (conf.debug) { if (conf.debug) {
console.log('previous public address',publicAddress, 'does not match current public address', addr); console.log('previous public address',publicAddress, 'does not match current public address', addr);
} }
publicAddress = addr;
setDeviceAddress(addr); await setDeviceAddress(addr);
} publicAddress = addr;
}, function (err) {
if (conf.debug) {
console.error('error getting public address', err);
}
});
});
} }
recheckPubAddr(); recheckPubAddr();

View File

@ -5,81 +5,81 @@ module.exports.create = function (deps, conf) {
var request = PromiseA.promisify(require('request')); var request = PromiseA.promisify(require('request'));
var pending = {}; var pending = {};
function checkPublicAddr(host) { async function checkPublicAddr(host) {
return request({ var result = await request({
method: 'GET' method: 'GET'
, url: host+'/api/org.oauth3.tunnel/checkip' , url: host+'/api/org.oauth3.tunnel/checkip'
, json: true , 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 crypto = require('crypto');
var token = crypto.randomBytes(8).toString('hex'); var token = crypto.randomBytes(8).toString('hex');
var keyAuth = crypto.randomBytes(32).toString('hex'); var keyAuth = crypto.randomBytes(32).toString('hex');
pending[token] = keyAuth; pending[token] = keyAuth;
var opts = { var reqObj = {
address: address
, port: port
, token: token
, keyAuthorization: keyAuth
, iat: Date.now()
};
return request({
method: 'POST' method: 'POST'
, url: host+'/api/org.oauth3.tunnel/loopback' , url: host+'/api/org.oauth3.tunnel/loopback'
, json: opts , json: {
}) address: address
.then(function (result) { , port: port
delete pending[token]; , token: token
if (!result.body) { , keyAuthorization: keyAuth
return PromiseA.reject(new Error('No response body in loopback request for port '+port)); , 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. var result;
if (conf.debug && result.body.error) { try {
console.log('error on remote side of port '+port+' loopback', result.body.error); result = await request(reqObj);
} } catch (err) {
return !!result.body.success;
}, function (err) {
delete pending[token]; delete pending[token];
throw err; 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) { async function loopback(provider) {
return deps.OAUTH3.discover(provider).then(function (directives) { var directives = await deps.OAUTH3.discover(provider);
return checkPublicAddr(directives.api).then(function (address) { var address = await checkPublicAddr(directives.api);
console.log('checking to see if', address, 'gets back to us'); 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);
}
var result = {error: null, address: address}; var ports = require('./servers').listeners.tcp.list();
ports.forEach(function (port, ind) { var values = await PromiseA.all(ports.map(function (port) {
result[port] = values[ind]; return checkSinglePort(directives.api, address, port);
}); }));
return result;
}); 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; loopback.checkPublicAddr = checkPublicAddr;