106 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			106 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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);
 | |
|     }
 | |
|     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'
 | |
|     , json: {
 | |
|         address: address
 | |
|       , port: port
 | |
|       , token: token
 | |
|       , keyAuthorization: keyAuth
 | |
|       , iat: Date.now()
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     var result;
 | |
|     try {
 | |
|       result = await deps.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;
 | |
|   }
 | |
| 
 | |
|   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) {
 | |
|       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;
 | |
| };
 |