'use strict'; module.exports.create = function (deps, conf) { var pending = {}; async function _checkPublicAddr(host) { var result = await deps.request({ method: 'GET' , url: deps.OAUTH3.url.normalize(host)+'/api/org.oauth3.tunnel/checkip' , json: true }); 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); } if (!result.body.address) { throw new Error("public address resonse doesn't contain address: "+JSON.stringify(result.body)); } return result.body.address; } async function checkPublicAddr(provider) { var directives = await deps.OAUTH3.discover(provider); return _checkPublicAddr(directives.api); } 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 reqObj = { method: 'POST' , url: deps.OAUTH3.url.normalize(host)+'/api/org.oauth3.tunnel/loopback' , timeout: 20*1000 , json: { address: address , port: port , token: token , keyAuthorization: keyAuth , iat: Date.now() , timeout: 18*1000 } }; var result; try { result = await deps.request(reqObj); } catch (err) { delete pending[token]; if (conf.debug) { console.log('error making loopback request for port ' + port + ' loopback', err.message); } return false; } delete pending[token]; if (!result.body) { if (conf.debug) { console.log('No response body in loopback request for port '+port); } return false; } // 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; } async function loopback(provider) { var directives = await deps.OAUTH3.discover(provider); var address = await _checkPublicAddr(directives.api); if (conf.debug) { console.log('checking to see if', address, 'gets back to us'); } var ports = require('../servers').listeners.tcp.list(); var values = await deps.PromiseA.all(ports.map(function (port) { return checkSinglePort(directives.api, address, port); })); if (conf.debug && Object.keys(pending).length) { console.log('remaining loopback tokens', pending); } return { address: address , ports: ports.reduce(function (obj, port, ind) { obj[port] = values[ind]; return obj; }, {}) }; } loopback.checkPublicAddr = checkPublicAddr; loopback.server = require('http').createServer(function (req, res) { var parsed = require('url').parse(req.url); var token = parsed.pathname.replace('/.well-known/cloud-challenge/', ''); if (pending[token]) { res.setHeader('Content-Type', 'text/plain'); res.end(pending[token]); } else { res.statusCode = 404; res.end(); } }); return loopback; };