Added failed login logging and reporting.

Ylian Saint-Hilaire committed Nov 17, 2019 at 16:20 UTC 32eb3fe72b2ea869b77d7ee8e30d71d0da2a75f5
4 files changed +18 -1
db.js
+2
@@ -693,6 +693,7 @@ module.exports.CreateDB = function (parent, func) {
693 obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
694 obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); };
695 obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); };
696 + obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null)?count:0); }); }
697
698 // Database actions on the power collection
699 obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); };
@@ -825,6 +826,7 @@ module.exports.CreateDB = function (parent, func) {
826 obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, func) { if (obj.databaseType == 1) { obj.eventsfile.find({ domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func); } else { obj.eventsfile.find({ domain: domain, nodeid: nodeid }, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func); } };
827 obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); };
828 obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); };
829 + obj.GetFailedLoginCount = function (username, domainid, lastlogin, func) { obj.eventsfile.count({ action: 'authfail', username: username, domain: domainid, time: { "$gte": lastlogin } }, function (err, count) { func((err == null) ? count : 0); }); }
830
831 // Database actions on the power collection
832 obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
meshuser.js
+8
@@ -360,6 +360,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
360 try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { }
361 }
362
363 + // See how many times bad login attempts where made since the last login
364 + const lastLoginTime = parent.users[user._id].pastlogin;
365 + if (lastLoginTime != null) {
366 + db.GetFailedLoginCount(user.name, user.domain, new Date(lastLoginTime * 1000), function (count) {
367 + if (count > 0) { try { ws.send(JSON.stringify({ action: 'msg', type: 'notify', title: "Security Warning", tag: 'ServerNotify', value: "There has been " + count + " failed login attempts on this account since the last login." })); } catch (ex) { } delete user.pastlogin; }
368 + });
369 + }
370 +
371 // We are all set, start receiving data
372 ws._socket.resume();
373 });
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.4.4-i",
3 + "version": "0.4.4-j",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
webserver.js
+7
@@ -659,6 +659,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
659 randomWaitTime = 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095); // This is a fail, wait a random time. 2 to 6 seconds.
660 req.session.messageid = 108; // Invalid token, try again.
661 parent.debug('web', 'handleLoginRequest: invalid 2FA token');
662 + obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr(req.ip) });
663 } else {
664 parent.debug('web', 'handleLoginRequest: 2FA token required');
665 }
@@ -686,12 +687,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
687 // Login failed, wait a random delay
688 setTimeout(function () {
689 // If the account is locked, display that.
690 + var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase();
691 if (err == 'locked') {
692 parent.debug('web', 'handleLoginRequest: login failed, locked account');
693 req.session.messageid = 110; // Account locked.
694 + obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'User login attempt on locked account from ' + cleanRemoteAddr(req.ip) });
695 } else {
696 parent.debug('web', 'handleLoginRequest: login failed, bad username and password');
697 req.session.messageid = 112; // Login failed, check username and password.
698 + obj.parent.DispatchEvent(['*', 'server-users', xuserid], obj, { action: 'authfail', userid: xuserid, username: xusername, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) });
699 }
700
701 // Clean up login mode and display password hint if present.
@@ -722,6 +726,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
726 }
727
728 // Save login time
729 + user.pastlogin = user.login;
730 user.login = Math.floor(Date.now() / 1000);
731 obj.db.SetUser(user);
732
@@ -1007,6 +1012,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1012 parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again');
1013 if ((req.body.token != null) || (req.body.hwtoken != null)) {
1014 req.session.messageid = 108; // Invalid token, try again.
1015 + obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + user.name], obj, { action: 'authfail', username: user.name, userid: 'user/' + domain.id + '/' + user.name, domain: domain.id, msg: 'User login attempt with incorrect 2nd factor from ' + cleanRemoteAddr(req.ip) });
1016 }
1017 req.session.loginmode = '5';
1018 req.session.tokenemail = email;
@@ -3467,6 +3473,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3473 } else {
3474 // If not authenticated, close the websocket connection
3475 parent.debug('web', 'ERR: Websocket bad user/pass auth');
3476 + //obj.parent.DispatchEvent(['*', 'server-users', 'user/' + domain.id + '/' + obj.args.user.toLowerCase()], obj, { action: 'authfail', userid: 'user/' + domain.id + '/' + obj.args.user.toLowerCase(), username: obj.args.user, domain: domain.id, msg: 'Invalid user login attempt from ' + cleanRemoteAddr(req.ip) });
3477 try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-2' })); ws.close(); } catch (e) { }
3478 }
3479 }