forked from coolaj86/goldilocks.js
		
	
		
			
				
	
	
		
			162 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| module.exports.create = function (deps, config) {
 | |
|   var PromiseA = require('bluebird');
 | |
|   var fs = PromiseA.promisifyAll(require('fs'));
 | |
|   var stunnel = require('stunnel');
 | |
|   var activeTunnels = {};
 | |
| 
 | |
|   var path = require('path');
 | |
|   var tokensPath = path.join(__dirname, '..', 'var', 'tokens.json');
 | |
|   var storage = {
 | |
|     _read: function () {
 | |
|       var tokens;
 | |
|       try {
 | |
|         tokens = require(tokensPath);
 | |
|       } catch (err) {
 | |
|         tokens = {};
 | |
|       }
 | |
|       return tokens;
 | |
|     }
 | |
|   , _write: function (tokens) {
 | |
|       return fs.mkdirAsync(path.dirname(tokensPath)).catch(function (err) {
 | |
|         if (err.code !== 'EEXIST') {
 | |
|           console.error('failed to mkdir', path.dirname(tokensPath), err.toString());
 | |
|         }
 | |
|       }).then(function () {
 | |
|         return fs.writeFileAsync(tokensPath, JSON.stringify(tokens), 'utf8');
 | |
|       });
 | |
|     }
 | |
| 
 | |
|   , all: function () {
 | |
|       var tokens = storage._read();
 | |
|       return PromiseA.resolve(Object.keys(tokens).map(function (key) {
 | |
|         return tokens[key];
 | |
|       }));
 | |
|     }
 | |
|   , save: function (result) {
 | |
|       var tokens = storage._read();
 | |
|       tokens[result.jwt] = result;
 | |
|       storage._write(tokens);
 | |
|     }
 | |
|   , del: function (id) {
 | |
|       var tokens = storage._read();
 | |
|       delete tokens[id];
 | |
|       storage._write(tokens);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   function addToken(data) {
 | |
|     if (!data.tunnelUrl) {
 | |
|       var decoded;
 | |
|       try {
 | |
|         decoded = JSON.parse(new Buffer(data.jwt.split('.')[1], 'base64').toString('ascii'));
 | |
|       } catch (err) {
 | |
|         console.warn('invalid web token given to tunnel manager', err);
 | |
|         return PromiseA.reject(err);
 | |
|       }
 | |
|       if (!decoded.aud) {
 | |
|         console.warn('tunnel manager given token with no tunnelUrl or audience');
 | |
|         var err = new Error('missing tunnelUrl and audience');
 | |
|         return PromiseA.reject(err);
 | |
|       }
 | |
|       data.tunnelUrl = 'wss://' + decoded.aud + '/';
 | |
|     }
 | |
| 
 | |
|     if (!activeTunnels[data.tunnelUrl]) {
 | |
|       console.log('creating new tunnel client for', data.tunnelUrl);
 | |
|       // We create the tunnel without an initial token so we can append the token and
 | |
|       // get the promise that should tell us more about if it worked or not.
 | |
|       activeTunnels[data.tunnelUrl] = stunnel.connect({
 | |
|         stunneld: data.tunnelUrl
 | |
|       , net: deps.tunnel.net
 | |
|         // NOTE: the ports here aren't that important since we are providing a custom
 | |
|         // `net.createConnection` that doesn't actually use the port. What is important
 | |
|         // is that any services we are interested in are listed in this object and have
 | |
|         // a '*' sub-property.
 | |
|       , services: {
 | |
|           https: { '*': 443 }
 | |
|         , http:  { '*': 80 }
 | |
|         , smtp:  { '*': 25 }
 | |
|         , smtps: { '*': 587 /*also 465/starttls*/ }
 | |
|         , ssh:   { '*': 22 }
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     console.log('appending token to tunnel at', data.tunnelUrl);
 | |
|     return activeTunnels[data.tunnelUrl].append(data.jwt);
 | |
|   }
 | |
| 
 | |
|   function removeToken(data) {
 | |
|     if (!data.tunnelUrl) {
 | |
|       var decoded;
 | |
|       try {
 | |
|         decoded = JSON.parse(new Buffer(data.jwt.split('.')[1], 'base64').toString('ascii'));
 | |
|       } catch (err) {
 | |
|         console.warn('invalid web token given to tunnel manager', err);
 | |
|         return PromiseA.reject(err);
 | |
|       }
 | |
|       if (!decoded.aud) {
 | |
|         console.warn('tunnel manager given token with no tunnelUrl or audience');
 | |
|         var err = new Error('missing tunnelUrl and audience');
 | |
|         return PromiseA.reject(err);
 | |
|       }
 | |
|       data.tunnelUrl = 'wss://' + decoded.aud + '/';
 | |
|     }
 | |
| 
 | |
|     // Not sure if we actually want to return an error that the token didn't even belong to a
 | |
|     // server that existed, but since it never existed we can consider it as "removed".
 | |
|     if (!activeTunnels[data.tunnelUrl]) {
 | |
|       return PromiseA.resolve();
 | |
|     }
 | |
| 
 | |
|     console.log('removing token from tunnel at', data.tunnelUrl);
 | |
|     return activeTunnels[data.tunnelUrl].clear(data.jwt);
 | |
|   }
 | |
| 
 | |
|   if (typeof config.tunnel === 'string') {
 | |
|     config.tunnel.split(',').forEach(function (jwt) {
 | |
|       addToken({ jwt: jwt, owner: 'config' });
 | |
|     });
 | |
|   }
 | |
|   storage.all().then(function (stored) {
 | |
|     stored.forEach(function (result) {
 | |
|       addToken(result);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   return {
 | |
|     add: function (data) {
 | |
|       return addToken(data).then(function () {
 | |
|         return storage.save(data);
 | |
|       });
 | |
|     }
 | |
|   , remove: function (data) {
 | |
|       return storage.del(data.jwt).then(function () {
 | |
|         return removeToken(data);
 | |
|       });
 | |
|     }
 | |
|   , get: function (owner) {
 | |
|       return storage.all().then(function (tokens) {
 | |
|         var result = {};
 | |
|         tokens.forEach(function (data) {
 | |
|           if (!result[data.owner]) {
 | |
|             result[data.owner] = {};
 | |
|           }
 | |
|           if (!result[data.owner][data.tunnelUrl]) {
 | |
|             result[data.owner][data.tunnelUrl] = [];
 | |
|           }
 | |
|           data.decoded = JSON.parse(new Buffer(data.jwt.split('.')[0], 'base64'));
 | |
|           result[data.owner][data.tunnelUrl].push(data);
 | |
|         });
 | |
| 
 | |
|         if (owner) {
 | |
|           return result[owner] || {};
 | |
|         }
 | |
|         return result;
 | |
|       });
 | |
|     }
 | |
|   };
 | |
| };
 |