Improved web relay cookie handling, #4172
Ylian Saint-Hilaire committed
Jun 29, 2022 at 14:16 UTC
22edf8d45f8f7b0704239639592be6eb87a75ab9
1 file changed
+41
-13
apprelays.js
+41
-13
@@ -84,7 +84,7 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
84
var tunnels = {};
85
86
// Any HTTP cookie set by the device is going to be shared between all tunnels to that device.
87
- obj.webCookie = null;
87
+ obj.webCookies = {};
88
89
// Events
90
obj.closed = false;
@@ -203,10 +203,11 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
203
204
// Construct the HTTP request
205
var request = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\r\n';
206
- request += 'host: ' + obj.addr + ':' + obj.port + '\r\n';
207
- const blockedHeaders = ['origin', 'host', 'cookie']; // These are headers we do not forward
206
+ const blockedHeaders = ['origin', 'cookie']; // These are headers we do not forward
207
for (var i in req.headers) { if (blockedHeaders.indexOf(i) == -1) { request += i + ': ' + req.headers[i] + '\r\n'; } }
209
- if (parent.webCookie != null) { request += 'cookie: ' + parent.webCookie + '\r\n' } // If we have a sessin cookie, use it.
208
+ var cookieStr = '';
209
+ for (var i in parent.webCookies) { if (cookieStr != '') { cookieStr += '; ' } cookieStr += (i + '=' + parent.webCookies[i].value); }
210
+ if (cookieStr.length > 0) { request += 'cookie: ' + cookieStr + '\r\n' } // If we have session cookies, set them in the header here
211
request += '\r\n';
212
213
if (req.headers['content-length'] != null) {
@@ -244,14 +245,13 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
245
if (i > 0) { baseurl = req.url.substring(0, i); }
246
if (baseurl.endsWith('/.websocket')) { req.url = baseurl.substring(0, baseurl.length - 11) + ((i < 1) ? '' : req.url.substring(i)); }
247
247
- //console.log('processWebSocket', obj.tunnelId, req.url);
248
-
249
- // Construct the HTTP request and send it out
248
+ // Construct the HTTP request
249
var request = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\r\n';
251
- request += 'host: ' + obj.addr + ':' + obj.port + '\r\n';
252
- const blockedHeaders = ['origin', 'host', 'cookie', 'sec-websocket-extensions']; // These are headers we do not forward
250
+ const blockedHeaders = ['origin', 'cookie', 'sec-websocket-extensions']; // These are headers we do not forward
251
for (var i in req.headers) { if (blockedHeaders.indexOf(i) == -1) { request += i + ': ' + req.headers[i] + '\r\n'; } }
254
- if (parent.webCookie != null) { request += 'cookie: ' + parent.webCookie + '\r\n' } // If we have a sessin cookie, use it.
252
+ var cookieStr = '';
253
+ for (var i in parent.webCookies) { if (cookieStr != '') { cookieStr += '; ' } cookieStr += (i + '=' + parent.webCookies[i].value); }
254
+ if (cookieStr.length > 0) { request += 'cookie: ' + cookieStr + '\r\n' } // If we have session cookies, set them in the header here
255
request += '\r\n';
256
send(Buffer.from(request));
257
@@ -445,7 +445,13 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
445
for (var i in obj.socketHeader) {
446
if (i != 0) {
447
var x2 = obj.socketHeader[i].indexOf(':');
448
- obj.socketXHeader[obj.socketHeader[i].substring(0, x2).toLowerCase()] = obj.socketHeader[i].substring(x2 + 2);
448
+ const n = obj.socketHeader[i].substring(0, x2).toLowerCase();
449
+ const v = obj.socketHeader[i].substring(x2 + 2);
450
+ if (n == 'set-cookie') { // Since "set-cookie" can be present many times in the header, handle it as an array of values
451
+ if (obj.socketXHeader[n] == null) { obj.socketXHeader[n] = [v]; } else { obj.socketXHeader[n].push(v); }
452
+ } else {
453
+ obj.socketXHeader[n] = v;
454
+ }
455
}
456
}
457
@@ -568,9 +574,31 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
574
// If there is a header, send it
575
if (header != null) {
576
obj.res.status(parseInt(header.Directive[1])); // Set the status
571
- const blockHeaders = ['Directive']; // These are headers we do not forward
577
+ const blockHeaders = ['Directive', 'sec-websocket-extensions']; // We do not forward these headers
578
for (var i in header) {
573
- if (i == 'set-cookie') { parent.webCookie = header[i]; } // Keep the cookie, don't forward it
579
+ if (i == 'set-cookie') {
580
+ for (var ii in header[i]) {
581
+ // Decode the new cookie
582
+ //console.log('set-cookie', header[i][ii]);
583
+ const cookieSplit = header[i][ii].split(';');
584
+ var newCookieName = null, newCookie = {};
585
+ for (var j in cookieSplit) {
586
+ var l = cookieSplit[j].indexOf('='), k = null, v = null;
587
+ if (l == -1) { k = cookieSplit[j].trim(); } else { k = cookieSplit[j].substring(0, l).trim(); v = cookieSplit[j].substring(l + 1).trim(); }
588
+ if (j == 0) { newCookieName = k; newCookie.value = v; } else { newCookie[k.toLowerCase()] = (v == null) ? true : v; }
589
+ }
590
+ if (newCookieName != null) {
591
+ if ((typeof newCookie['max-age'] == 'string') && (parseInt(newCookie['max-age']) <= 0)) {
592
+ delete parent.webCookies[newCookieName]; // Remove a expired cookie
593
+ //console.log('clear-cookie', newCookieName);
594
+ } else if (((newCookie.secure != true) || (obj.tls != null))) {
595
+ parent.webCookies[newCookieName] = newCookie; // Keep this cookie in the session
596
+ if (newCookie.httponly != true) { obj.res.set(i, header[i]); } // if the cookie is not HTTP-only, forward it to the browser. We need to do this to allow JavaScript to read it.
597
+ //console.log('new-cookie', newCookieName, newCookie);
598
+ }
599
+ }
600
+ }
601
+ }
602
else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i, header[i]); } // Set the headers if not blocked
603
}
604
obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future