diff --git a/db.js b/db.js index 2e882e74..4220c1bb 100644 --- a/db.js +++ b/db.js @@ -693,6 +693,7 @@ module.exports.CreateDB = function (parent, func) { 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); }; obj.RemoveAllEvents = function (domain) { obj.eventsfile.deleteMany({ domain: domain }, { multi: true }); }; obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.deleteMany({ domain: domain, nodeid: nodeid }, { multi: true }); }; + 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); }); } // Database actions on the power collection obj.getAllPower = function (func) { obj.powerfile.find({}).toArray(func); }; @@ -825,6 +826,7 @@ module.exports.CreateDB = function (parent, func) { 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); } }; obj.RemoveAllEvents = function (domain) { obj.eventsfile.remove({ domain: domain }, { multi: true }); }; obj.RemoveAllNodeEvents = function (domain, nodeid) { obj.eventsfile.remove({ domain: domain, nodeid: nodeid }, { multi: true }); }; + 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); }); } // Database actions on the power collection obj.getAllPower = function (func) { obj.powerfile.find({}, func); }; diff --git a/meshuser.js b/meshuser.js index 4c43f42c..c1189ccb 100644 --- a/meshuser.js +++ b/meshuser.js @@ -360,6 +360,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { } } + // See how many times bad login attempts where made since the last login + const lastLoginTime = parent.users[user._id].pastlogin; + if (lastLoginTime != null) { + db.GetFailedLoginCount(user.name, user.domain, new Date(lastLoginTime * 1000), function (count) { + 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; } + }); + } + // We are all set, start receiving data ws._socket.resume(); }); diff --git a/package.json b/package.json index a375270a..d6df1cf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.4-i", + "version": "0.4.4-j", "keywords": [ "Remote Management", "Intel AMT", diff --git a/webserver.js b/webserver.js index 2fd124fb..3cfe8306 100644 --- a/webserver.js +++ b/webserver.js @@ -659,6 +659,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { randomWaitTime = 2000 + (obj.crypto.randomBytes(2).readUInt16BE(0) % 4095); // This is a fail, wait a random time. 2 to 6 seconds. req.session.messageid = 108; // Invalid token, try again. parent.debug('web', 'handleLoginRequest: invalid 2FA token'); + 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) }); } else { parent.debug('web', 'handleLoginRequest: 2FA token required'); } @@ -686,12 +687,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Login failed, wait a random delay setTimeout(function () { // If the account is locked, display that. + var xuserid = 'user/' + domain.id + '/' + xusername.toLowerCase(); if (err == 'locked') { parent.debug('web', 'handleLoginRequest: login failed, locked account'); req.session.messageid = 110; // Account locked. + 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) }); } else { parent.debug('web', 'handleLoginRequest: login failed, bad username and password'); req.session.messageid = 112; // Login failed, check username and password. + 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) }); } // Clean up login mode and display password hint if present. @@ -722,6 +726,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Save login time + user.pastlogin = user.login; user.login = Math.floor(Date.now() / 1000); obj.db.SetUser(user); @@ -1007,6 +1012,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.debug('web', 'handleResetAccountRequest: Invalid 2FA token, try again'); if ((req.body.token != null) || (req.body.hwtoken != null)) { req.session.messageid = 108; // Invalid token, try again. + 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) }); } req.session.loginmode = '5'; req.session.tokenemail = email; @@ -3467,6 +3473,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } else { // If not authenticated, close the websocket connection parent.debug('web', 'ERR: Websocket bad user/pass auth'); + //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) }); try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-2' })); ws.close(); } catch (e) { } } }