changed token handling to allow multiple per websocket
This commit is contained in:
		
							parent
							
								
									65df12ecb3
								
							
						
					
					
						commit
						40c797b729
					
				
							
								
								
									
										225
									
								
								wstunneld.js
									
									
									
									
									
								
							
							
						
						
									
										225
									
								
								wstunneld.js
									
									
									
									
									
								
							| @ -23,8 +23,7 @@ Devices.remove = function (store, servername, device) { | |||||||
|   var index = devices.indexOf(device); |   var index = devices.indexOf(device); | ||||||
| 
 | 
 | ||||||
|   if (index < 0) { |   if (index < 0) { | ||||||
|     var id = device.deviceId || device.servername || device.id; |     console.warn('attempted to remove non-present device', device.deviceId, 'from', servername); | ||||||
|     console.warn('attempted to remove non-present device', id, 'from', servername); |  | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   return devices.splice(index, 1)[0]; |   return devices.splice(index, 1)[0]; | ||||||
| @ -55,75 +54,26 @@ module.exports.create = function (copts) { | |||||||
|   var pongTimeout = copts.pongTimeout || 10*1000; |   var pongTimeout = copts.pongTimeout || 10*1000; | ||||||
| 
 | 
 | ||||||
|   function onWsConnection(ws) { |   function onWsConnection(ws) { | ||||||
|     var location = url.parse(ws.upgradeReq.url, true); |     var socketId = packer.socketToId(ws.upgradeReq.socket); | ||||||
|     var authn = (ws.upgradeReq.headers.authorization||'').split(/\s+/); |     var remotes = {}; | ||||||
|     var jwtoken; |  | ||||||
|     var token; |  | ||||||
| 
 | 
 | ||||||
|     try { |     function logName() { | ||||||
|       if (authn[0]) { |       var result = Object.keys(remotes).map(function (jwtoken) { | ||||||
|         if ('basic' === authn[0].toLowerCase()) { |         return remotes[jwtoken].deviceId; | ||||||
|           authn = new Buffer(authn[1], 'base64').toString('ascii').split(':'); |       }).join(';'); | ||||||
|         } | 
 | ||||||
|         /* |       return result || socketId; | ||||||
|         if (-1 !== [ 'bearer', 'jwk' ].indexOf(authn[0].toLowerCase())) { |  | ||||||
|           jwtoken = authn[1]; |  | ||||||
|         } |  | ||||||
|         */ |  | ||||||
|       } |  | ||||||
|       jwtoken = authn[1] || location.query.access_token; |  | ||||||
|     } catch(e) { |  | ||||||
|       jwtoken = null; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     try { |  | ||||||
|       token = jwt.verify(jwtoken, copts.secret); |  | ||||||
|     } catch(e) { |  | ||||||
|       token = null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /* |  | ||||||
|     if (!token || !token.name) { |  | ||||||
|       console.log('location, token'); |  | ||||||
|       console.log(location.query.access_token); |  | ||||||
|       console.log(token); |  | ||||||
|     } |  | ||||||
|     */ |  | ||||||
| 
 |  | ||||||
|     if (!token) { |  | ||||||
|       ws.send(JSON.stringify({ error: { message: "invalid access token", code: "E_INVALID_TOKEN" } })); |  | ||||||
|       ws.close(); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     //console.log('[wstunneld.js] DEBUG', token);
 |  | ||||||
| 
 |  | ||||||
|     if (!Array.isArray(token.domains)) { |  | ||||||
|       if ('string' === typeof token.name) { |  | ||||||
|         token.domains = [ token.name ]; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!Array.isArray(token.domains)) { |  | ||||||
|       ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); |  | ||||||
|       ws.close(); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     var remote = {}; |  | ||||||
|     remote.ws = ws; |  | ||||||
|     remote.servername = (token.device && token.device.hostname) || token.domains.join(','); |  | ||||||
|     remote.deviceId = (token.device && token.device.id) || null; |  | ||||||
|     remote.id = packer.socketToId(ws.upgradeReq.socket); |  | ||||||
|     console.log("remote.id", remote.id); |  | ||||||
|     remote.domains = token.domains; |  | ||||||
|     remote.clients = {}; |  | ||||||
|     // TODO allow tls to be decrypted by server if client is actually a browser
 |  | ||||||
|     // and we haven't implemented tls in the browser yet
 |  | ||||||
|     // remote.decrypt = token.decrypt;
 |  | ||||||
| 
 |  | ||||||
|     function closeBrowserConn(cid) { |     function closeBrowserConn(cid) { | ||||||
|       if (!remote.clients[cid]) { |       var remote; | ||||||
|  |       Object.keys(remotes).some(function (jwtoken) { | ||||||
|  |         if (remotes[jwtoken].clients[cid]) { | ||||||
|  |           remote = remotes[jwtoken]; | ||||||
|  |           return true; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       if (!remote) { | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -151,20 +101,110 @@ module.exports.create = function (copts) { | |||||||
|         ; |         ; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     function addToken(jwtoken) { | ||||||
|  |       if (remotes[jwtoken]) { | ||||||
|  |         ws.send(JSON.stringify({ error: { message: "token sent multiple times", code: "E_TOKEN_REPEAT" } })); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       var token; | ||||||
|  |       try { | ||||||
|  |         token = jwt.verify(jwtoken, copts.secret); | ||||||
|  |       } catch (e) { | ||||||
|  |         token = null; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!token) { | ||||||
|  |         ws.send(JSON.stringify({ error: { message: "invalid access token", code: "E_INVALID_TOKEN" } })); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!Array.isArray(token.domains)) { | ||||||
|  |         if ('string' === typeof token.name) { | ||||||
|  |           token.domains = [ token.name ]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!Array.isArray(token.domains)) { | ||||||
|  |         ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |       if (token.domains.some(function (name) { return typeof name !== 'string'; })) { | ||||||
|  |         ws.send(JSON.stringify({ error: { message: "invalid server name", code: "E_INVALID_NAME" } })); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Add the custom properties we need to manage this remote, then add it to all the relevant
 | ||||||
|  |       // domains and the list of all this websocket's remotes.
 | ||||||
|  |       token.deviceId = (token.device && (token.device.id || token.device.hostname)) || token.domains.join(','); | ||||||
|  |       token.ws = ws; | ||||||
|  |       token.clients = {}; | ||||||
|  | 
 | ||||||
|  |       token.domains.forEach(function (domainname) { | ||||||
|  |         console.log('domainname', domainname); | ||||||
|  |         Devices.add(deviceLists, domainname, token); | ||||||
|  |       }); | ||||||
|  |       remotes[jwtoken] = token; | ||||||
|  |       console.log("added token '" + token.deviceId + "' to websocket", socketId); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function removeToken(jwtoken) { | ||||||
|  |       var remote = remotes[jwtoken]; | ||||||
|  |       if (!remote) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Prevent any more browser connections being sent to this remote, and any existing
 | ||||||
|  |       // connections from trying to send more data across the connection.
 | ||||||
|  |       remote.domains.forEach(function (domainname) { | ||||||
|  |         Devices.remove(deviceLists, domainname, remote); | ||||||
|  |       }); | ||||||
|  |       remote.ws = null; | ||||||
|  | 
 | ||||||
|  |       // Close all of the existing browser connections associated with this websocket connection.
 | ||||||
|  |       Object.keys(remote.clients).forEach(function (cid) { | ||||||
|  |         closeBrowserConn(cid); | ||||||
|  |       }); | ||||||
|  |       delete remotes[jwtoken]; | ||||||
|  |       console.log("removed token '" + remote.deviceId + "' from websocket", socketId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var firstToken; | ||||||
|  |     var authn = (ws.upgradeReq.headers.authorization||'').split(/\s+/); | ||||||
|  |     if (authn[0] && 'basic' === authn[0].toLowerCase()) { | ||||||
|  |       try { | ||||||
|  |         authn = new Buffer(authn[1], 'base64').toString('ascii').split(':'); | ||||||
|  |         firstToken = authn[1]; | ||||||
|  |       } catch (err) { } | ||||||
|  |     } | ||||||
|  |     if (!firstToken) { | ||||||
|  |       firstToken = url.parse(ws.upgradeReq.url, true).query.access_token; | ||||||
|  |     } | ||||||
|  |     if (firstToken && !addToken(firstToken)) { | ||||||
|  |       ws.close(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     var handlers = { |     var handlers = { | ||||||
|       onmessage: function (opts) { |       onmessage: function (opts) { | ||||||
|         // opts.data
 |  | ||||||
|         var cid = packer.addrToId(opts); |         var cid = packer.addrToId(opts); | ||||||
|         var browserConn = remote.clients[cid]; |         console.log("remote '" + logName() + "' has data for '" + cid + "'", opts.data.byteLength); | ||||||
| 
 | 
 | ||||||
|         console.log("remote '" + remote.servername + " : " + remote.id + "' has data for '" + cid + "'", opts.data.byteLength); |         var browserConn; | ||||||
|  |         Object.keys(remotes).some(function (jwtoken) { | ||||||
|  |           if (remotes[jwtoken].clients[cid]) { | ||||||
|  |             browserConn = remotes[jwtoken].clients[cid]; | ||||||
|  |             return true; | ||||||
|  |           } | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         if (!browserConn) { |         if (browserConn) { | ||||||
|           remote.ws.send(packer.pack(opts, null, 'error')); |           browserConn.write(opts.data); | ||||||
|           return; |         } | ||||||
|  |         else { | ||||||
|  |           ws.send(packer.pack(opts, null, 'error')); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         browserConn.write(opts.data); |  | ||||||
|       } |       } | ||||||
|     , onend: function (opts) { |     , onend: function (opts) { | ||||||
|         var cid = packer.addrToId(opts); |         var cid = packer.addrToId(opts); | ||||||
| @ -177,14 +217,7 @@ module.exports.create = function (copts) { | |||||||
|         closeBrowserConn(cid); |         closeBrowserConn(cid); | ||||||
|       } |       } | ||||||
|     }; |     }; | ||||||
|     remote.unpacker = packer.create(handlers); |     var unpacker = packer.create(handlers); | ||||||
| 
 |  | ||||||
|     // Now that we have created our remote object we need to store it in the deviceList for
 |  | ||||||
|     // each domainname we are supposed to be handling.
 |  | ||||||
|     token.domains.forEach(function (domainname) { |  | ||||||
|       console.log('domainname', domainname); |  | ||||||
|       Devices.add(deviceLists, domainname, remote); |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     var lastActivity = Date.now(); |     var lastActivity = Date.now(); | ||||||
|     var timeoutId; |     var timeoutId; | ||||||
| @ -204,11 +237,11 @@ module.exports.create = function (copts) { | |||||||
|       // Otherwise we check to see if the pong has also timed out, and if not we send a ping
 |       // Otherwise we check to see if the pong has also timed out, and if not we send a ping
 | ||||||
|       // and call this function again when the pong will have timed out.
 |       // and call this function again when the pong will have timed out.
 | ||||||
|       else if (silent < activityTimeout + pongTimeout) { |       else if (silent < activityTimeout + pongTimeout) { | ||||||
|         console.log('pinging', remote.deviceId || remote.servername); |         console.log('pinging', logName()); | ||||||
|         try { |         try { | ||||||
|           remote.ws.ping(); |           ws.ping(); | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           console.warn('failed to ping home cloud', remote.deviceId || remote.servername); |           console.warn('failed to ping home cloud', logName()); | ||||||
|         } |         } | ||||||
|         timeoutId = setTimeout(checkTimeout, pongTimeout); |         timeoutId = setTimeout(checkTimeout, pongTimeout); | ||||||
|       } |       } | ||||||
| @ -216,8 +249,8 @@ module.exports.create = function (copts) { | |||||||
|       // Last case means the ping we sent before didn't get a response soon enough, so we
 |       // Last case means the ping we sent before didn't get a response soon enough, so we
 | ||||||
|       // need to close the websocket connection.
 |       // need to close the websocket connection.
 | ||||||
|       else { |       else { | ||||||
|         console.log('home cloud', remote.deviceId || remote.servername, 'connection timed out'); |         console.log('home cloud', logName(), 'connection timed out'); | ||||||
|         remote.ws.close(1013, 'connection timeout'); |         ws.close(1013, 'connection timeout'); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     timeoutId = setTimeout(checkTimeout, activityTimeout); |     timeoutId = setTimeout(checkTimeout, activityTimeout); | ||||||
| @ -230,22 +263,14 @@ module.exports.create = function (copts) { | |||||||
|       refreshTimeout(); |       refreshTimeout(); | ||||||
|       console.log('message from home cloud to tunneler to browser', chunk.byteLength); |       console.log('message from home cloud to tunneler to browser', chunk.byteLength); | ||||||
|       //console.log(chunk.toString());
 |       //console.log(chunk.toString());
 | ||||||
|       remote.unpacker.fns.addChunk(chunk); |       unpacker.fns.addChunk(chunk); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function hangup() { |     function hangup() { | ||||||
|       clearTimeout(timeoutId); |       clearTimeout(timeoutId); | ||||||
|       console.log('home cloud', remote.deviceId || remote.servername, 'connection closing'); |       console.log('home cloud', logName(), 'connection closing'); | ||||||
|       // Prevent any more browser connections being sent to this remote, and any existing
 |       Object.keys(remotes).forEach(function (jwtoken) { | ||||||
|       // connections from trying to send more data across the connection.
 |         removeToken(jwtoken); | ||||||
|       token.domains.forEach(function (domainname) { |  | ||||||
|         Devices.remove(deviceLists, domainname, remote); |  | ||||||
|       }); |  | ||||||
|       remote.ws = null; |  | ||||||
| 
 |  | ||||||
|       // Close all of the existing browser connections associated with this websocket connection.
 |  | ||||||
|       Object.keys(remote.clients).forEach(function (cid) { |  | ||||||
|         closeBrowserConn(cid); |  | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user