Added user session destruction on logout for improved security.
Ylian Saint-Hilaire committed
Jan 6, 2022 at 15:05 UTC
933b9af89940a528ec815bb050fde5ede799c39e
2 files changed
+56
-2
package.json
+14
-2
@@ -36,6 +36,9 @@
36
"sample-config-advanced.json"
37
],
38
"dependencies": {
39
+ "@yetzt/nedb": "^1.8.0",
40
+ "archiver": "^4.0.2",
41
+ "archiver-zip-encrypted": "^1.0.10",
42
"body-parser": "^1.19.0",
43
"cbor": "~5.2.0",
44
"compression": "^1.7.4",
@@ -43,13 +46,22 @@
46
"express": "^4.17.0",
47
"express-handlebars": "^3.1.0",
48
"express-ws": "^4.0.0",
49
+ "image-size": "^1.0.0",
50
"ipcheck": "^0.1.0",
51
+ "loadavg-windows": "^1.1.1",
52
"minimist": "^1.2.5",
53
"multiparty": "^4.2.1",
49
- "@yetzt/nedb": "^1.8.0",
54
"node-forge": "^0.10.0",
55
+ "node-rdpjs-2": "^0.3.5",
56
+ "node-windows": "^0.1.4",
57
+ "otplib": "^10.2.3",
58
+ "pg": "^8.7.1",
59
+ "pgtools": "^0.3.2",
60
+ "ssh2": "^1.5.0",
61
+ "web-push": "^3.4.5",
62
"ws": "^5.2.3",
52
- "yauzl": "^2.10.0"
63
+ "yauzl": "^2.10.0",
64
+ "yubikeyotp": "^0.2.0"
65
},
66
"engines": {
67
"node": ">=10.0.0"
webserver.js
+42
@@ -84,6 +84,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
84
obj.blockedAgents = 0;
85
obj.renderPages = null;
86
obj.renderLanguages = [];
87
+ obj.destroyedSessions = {};
88
89
// Mesh Rights
90
const MESHRIGHT_EDITMESH = 0x00000001;
@@ -768,6 +769,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
769
if (req.session.userid) {
770
var user = obj.users[req.session.userid];
771
if (user != null) { obj.parent.DispatchEvent(['*'], obj, { etype: 'user', userid: user._id, username: user.name, action: 'logout', msgid: 2, msg: 'Account logout', domain: domain.id }); }
772
+ if (req.session.x) { clearDestroyedSessions(); obj.destroyedSessions[req.session.userid + '/' + req.session.x] = Date.now(); } // Destroy this session
773
}
774
req.session = null;
775
parent.debug('web', 'handleLogoutRequest: success.');
@@ -1260,6 +1262,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
1262
delete req.session.currentNode;
1263
req.session.userid = userid;
1264
req.session.ip = req.clientIp;
1265
+ setSessionRandom(req);
1266
1267
// If a login token was used, add this information and expire time to the session.
1268
if ((loginOptions != null) && (loginOptions.tokenName != null) && (loginOptions.tokenUser != null)) {
@@ -1423,6 +1426,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
1426
obj.users[user._id] = user;
1427
req.session.userid = user._id;
1428
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1429
+ setSessionRandom(req);
1430
// Create a user, generate a salt and hash the password
1431
require('./pass').hash(req.body.password1, function (err, salt, hash, tag) {
1432
if (err) throw err;
@@ -1531,6 +1535,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
1535
parent.debug('web', 'handleResetPasswordRequest: success');
1536
req.session.userid = userid;
1537
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
1538
+ setSessionRandom(req);
1539
completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tuser, req.session.tpass, direct, loginOptions);
1540
}, 0);
1541
}
@@ -2425,6 +2430,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2430
parent.DispatchEvent(targets, obj, event);
2431
2432
req.session.userid = userid;
2433
+ setSessionRandom(req);
2434
} else {
2435
// New users not allowed
2436
parent.debug('web', 'handleStrategyLogin: Can\'t create new accounts');
@@ -2449,6 +2455,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2455
}
2456
parent.debug('web', 'handleStrategyLogin: succesful login: ' + userid);
2457
req.session.userid = userid;
2458
+ setSessionRandom(req);
2459
}
2460
}
2461
//res.redirect(domain.url); // This does not handle cookie correctly.
@@ -2500,6 +2507,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2507
req.session.userid = userid;
2508
delete req.session.currentNode;
2509
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2510
+ setSessionRandom(req);
2511
handleRootRequestEx(req, res, domain, direct);
2512
});
2513
} else if ((req.session != null) && (typeof req.session.loginToken == 'string')) {
@@ -2531,6 +2539,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2539
req.session.userid = 'user/' + domain.id + '/~';
2540
delete req.session.currentNode;
2541
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2542
+ setSessionRandom(req);
2543
if (obj.users[req.session.userid] == null) {
2544
// Create the dummy user ~ with impossible password
2545
parent.debug('web', 'handleRootRequestEx: created dummy user in nouser mode.');
@@ -2544,6 +2553,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2553
req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
2554
delete req.session.currentNode;
2555
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2556
+ setSessionRandom(req);
2557
} else if (req.query.login && (obj.parent.loginCookieEncryptionKey != null)) {
2558
var loginCookie = obj.parent.decodeCookie(req.query.login, obj.parent.loginCookieEncryptionKey, 60); // 60 minute timeout
2559
//if ((loginCookie != null) && (obj.args.cookieipcheck !== false) && (loginCookie.ip != null) && (loginCookie.ip != req.clientIp)) { loginCookie = null; } // If the cookie if binded to an IP address, check here.
@@ -2554,6 +2564,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2564
req.session.userid = loginCookie.u;
2565
delete req.session.currentNode;
2566
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2567
+ setSessionRandom(req);
2568
} else {
2569
parent.debug('web', 'handleRootRequestEx: cookie auth failed.');
2570
}
@@ -2570,6 +2581,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2581
req.session.usersGroups = req.connection.userGroups;
2582
delete req.session.currentNode;
2583
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
2584
+ setSessionRandom(req);
2585
2586
// Check if this user exists, create it if not.
2587
user = obj.users[req.session.userid];
@@ -5576,6 +5588,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5588
5589
// Add HTTP security headers to all responses
5590
obj.app.use(function (req, res, next) {
5591
+ // Check if a session is destroyed
5592
+ if (typeof req.session.userid == 'string') {
5593
+ if (typeof req.session.x == 'string') {
5594
+ if (obj.destroyedSessions[req.session.userid + '/' + req.session.x] != null) {
5595
+ delete req.session.userid;
5596
+ delete req.session.ip;
5597
+ delete req.session.t;
5598
+ delete req.session.x;
5599
+ }
5600
+ } else {
5601
+ // Legacy session without a random, add one.
5602
+ setSessionRandom(req);
5603
+ }
5604
+ }
5605
5606
// Remove legacy values from the session to keep the session as small as possible
5607
delete req.session.domainid;
@@ -7932,5 +7958,21 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
7958
}
7959
}
7960
7961
+ // Set a random value to this session. Only works if the session has a userid.
7962
+ // This random value along with the userid is used to destroy the session when logging out.
7963
+ function setSessionRandom(req) {
7964
+ if ((req.session == null) || (req.session.userid == null) || (req.session.x != null)) return;
7965
+ var x = obj.crypto.randomBytes(6).toString('base64');
7966
+ while (obj.destroyedSessions[req.session.userid + '/' + x] != null) { x = obj.crypto.randomBytes(6).toString('base64'); }
7967
+ req.session.x = x;
7968
+ }
7969
+
7970
+ // Remove all destroyed sessions after 2 hours, these sessions would have timed out anyway.
7971
+ function clearDestroyedSessions() {
7972
+ var toRemove = [], t = Date.now() - (2 * 60 * 60 * 1000);
7973
+ for (var i in obj.destroyedSessions) { if (obj.destroyedSessions[i] < t) { toRemove.push(i); } }
7974
+ for (var i in toRemove) { delete obj.destroyedSessions[toRemove[i]]; }
7975
+ }
7976
+
7977
return obj;
7978
};