| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports.create = function (deps, config) { | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |   var PromiseA = require('bluebird'); | 
					
						
							|  |  |  |   var fs = PromiseA.promisifyAll(require('fs')); | 
					
						
							| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  |   var stunnel = require('stunnel'); | 
					
						
							|  |  |  |   var activeTunnels = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-01 13:06:52 -06:00
										 |  |  |   var path = require('path'); | 
					
						
							|  |  |  |   var tokensPath = path.join(__dirname, '..', 'var', 'tokens.json'); | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |   var storage = { | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |     _read: function () { | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |       var tokens; | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         tokens = require(tokensPath); | 
					
						
							|  |  |  |       } catch (err) { | 
					
						
							|  |  |  |         tokens = {}; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |       return tokens; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   , _write: function (tokens) { | 
					
						
							| 
									
										
										
										
											2017-06-01 13:06:52 -06:00
										 |  |  |       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'); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |   , all: function () { | 
					
						
							|  |  |  |       var tokens = storage._read(); | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |       return PromiseA.resolve(Object.keys(tokens).map(function (key) { | 
					
						
							|  |  |  |         return tokens[key]; | 
					
						
							|  |  |  |       })); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   , save: function (result) { | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |       var tokens = storage._read(); | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |       tokens[result.jwt] = result; | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |       storage._write(tokens); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   , del: function (id) { | 
					
						
							|  |  |  |       var tokens = storage._read(); | 
					
						
							|  |  |  |       delete tokens[id]; | 
					
						
							|  |  |  |       storage._write(tokens); | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  |   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); | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |         return PromiseA.reject(err); | 
					
						
							| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  |       } | 
					
						
							|  |  |  |       if (!decoded.aud) { | 
					
						
							|  |  |  |         console.warn('tunnel manager given token with no tunnelUrl or audience'); | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |         var err = new Error('missing tunnelUrl and audience'); | 
					
						
							|  |  |  |         return PromiseA.reject(err); | 
					
						
							| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  |       } | 
					
						
							|  |  |  |       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); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |   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); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  |   if (typeof config.tunnel === 'string') { | 
					
						
							|  |  |  |     config.tunnel.split(',').forEach(function (jwt) { | 
					
						
							|  |  |  |       addToken({ jwt: jwt, owner: 'config' }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |   storage.all().then(function (stored) { | 
					
						
							|  |  |  |     stored.forEach(function (result) { | 
					
						
							|  |  |  |       addToken(result); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							| 
									
										
										
										
											2017-05-29 13:41:09 -06:00
										 |  |  |     add: function (data) { | 
					
						
							|  |  |  |       return addToken(data).then(function () { | 
					
						
							|  |  |  |         return storage.save(data); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-05-29 15:14:37 -06:00
										 |  |  |   , remove: function (data) { | 
					
						
							|  |  |  |       return storage.del(data.jwt).then(function () { | 
					
						
							|  |  |  |         return removeToken(data); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-05-30 12:15:19 -06:00
										 |  |  |   , 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; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-05-26 12:11:39 -06:00
										 |  |  |   }; | 
					
						
							|  |  |  | }; |