mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-09-11 08:55:32 +03:00
More work on login tokens.
This commit is contained in:
parent
4231f4071b
commit
6c3e010ce9
6
db.js
6
db.js
@ -1275,7 +1275,7 @@ module.exports.CreateDB = function (parent, func) {
|
||||
obj.GetEventsWithLimit = function (ids, domain, limit, func) { obj.eventsfile.find({ domain: domain, ids: { $in: ids } }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
|
||||
obj.GetUserEvents = function (ids, domain, username, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).toArray(func); };
|
||||
obj.GetUserEventsWithLimit = function (ids, domain, username, limit, func) { obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }, { username: username }] }).project({ type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).toArray(func); };
|
||||
obj.GetUserLoginEvents = function (domain, userid, func) { obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }).project({ action: 1, time: 1, msgid: 1, msgArgs: 1 }).sort({ time: -1 }).toArray(func); };
|
||||
obj.GetUserLoginEvents = function (domain, userid, func) { obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }).project({ action: 1, time: 1, msgid: 1, msgArgs: 1, tokenName: 1 }).sort({ time: -1 }).toArray(func); };
|
||||
obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { obj.eventsfile.find({ domain: domain, nodeid: nodeid }).project({ type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).toArray(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 }); };
|
||||
@ -1463,9 +1463,9 @@ module.exports.CreateDB = function (parent, func) {
|
||||
};
|
||||
obj.GetUserLoginEvents = function (domain, userid, func) {
|
||||
if (obj.databaseType == 1) {
|
||||
obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }, { action: 1, time: 1, msgid: 1, msgArgs: 1 }).sort({ time: -1 }).exec(func);
|
||||
obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }, { action: 1, time: 1, msgid: 1, msgArgs: 1, tokenName: 1 }).sort({ time: -1 }).exec(func);
|
||||
} else {
|
||||
obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }, { action: 1, time: 1, msgid: 1, msgArgs: 1 }).sort({ time: -1 }, func);
|
||||
obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }, { action: 1, time: 1, msgid: 1, msgArgs: 1, tokenName: 1 }).sort({ time: -1 }, func);
|
||||
}
|
||||
};
|
||||
obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, func) { if (obj.databaseType == 1) { 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).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); } };
|
||||
|
@ -5543,7 +5543,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||
db.GetUserLoginEvents(domain.id, user._id, function (err, docs) {
|
||||
if (err != null) return;
|
||||
var e = [];
|
||||
for (var i in docs) { e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs }); }
|
||||
for (var i in docs) { e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs, tn: docs[i].tokenName }); }
|
||||
try { ws.send(JSON.stringify({ action: 'previousLogins', events: e })); } catch (ex) { }
|
||||
});
|
||||
} else {
|
||||
@ -5553,7 +5553,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||
var e = [];
|
||||
for (var i in docs) {
|
||||
if ((docs[i].msgArgs) && (docs[i].userid == user._id) && ((docs[i].action == 'authfail') || (docs[i].action == 'login'))) {
|
||||
e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs });
|
||||
e.push({ t: docs[i].time, m: docs[i].msgid, a: docs[i].msgArgs, tn: docs[i].tokenName });
|
||||
}
|
||||
}
|
||||
try { ws.send(JSON.stringify({ action: 'previousLogins', events: e })); } catch (ex) { }
|
||||
|
@ -3334,7 +3334,7 @@
|
||||
x += '<div style=max-height:260px;overflow-y:scroll;overflow-x:hidden><table>';
|
||||
for (var i in message.events) {
|
||||
var m = message.events[i].m;
|
||||
if (m == 107) { m = "Valid login"; c = 'BBD1BB'; xx = ''; }
|
||||
if (m == 107) { m = "Valid login"; c = 'BBD1BB'; xx = ''; if (message.events[i].tn != null) { m = format("Token: {0}", message.events[i].tn); c = '88D188' } }
|
||||
else if (m == 108) { m = "Invalid 2FA"; c ='DD9DC3'; xx = 'x'; }
|
||||
else if (m == 109) { m = "Locked account"; c ='E1BBBB'; xx = 'x'; }
|
||||
else if (m == 110) { m = "Invalid password"; c = 'E1BBBB'; xx = 'x'; }
|
||||
|
47
webserver.js
47
webserver.js
@ -588,9 +588,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
|
||||
|
||||
// Succesful login token authentication
|
||||
var loginOptions = { logintoken: 1 };
|
||||
var loginOptions = { tokenName: loginToken.name, tokenUser: loginToken.tokenUser };
|
||||
if (loginToken.expire != 0) { loginOptions.expire = loginToken.expire; }
|
||||
return fn(null, user._id, loginOptions);
|
||||
return fn(null, user._id, null, loginOptions);
|
||||
}
|
||||
fn(new Error('invalid password'));
|
||||
}, 0);
|
||||
@ -713,7 +713,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
}
|
||||
|
||||
// Return true if this user has 2-step auth active
|
||||
function checkUserOneTimePasswordRequired(domain, user, req) {
|
||||
function checkUserOneTimePasswordRequired(domain, user, req, loginOptions) {
|
||||
// If this login occured using a login token, no 2FA needed.
|
||||
if ((loginOptions != null) && (typeof loginOptions.tokenName === 'string')) { return false; }
|
||||
|
||||
// Check if we can skip 2nd factor auth because of the source IP address
|
||||
if ((req != null) && (req.clientIp != null) && (domain.passwordrequirements != null) && (domain.passwordrequirements.skip2factor != null)) {
|
||||
for (var i in domain.passwordrequirements.skip2factor) { if (require('ipcheck').match(req.clientIp, domain.passwordrequirements.skip2factor[i]) === true) return false; }
|
||||
@ -935,7 +938,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
if ((xusername == null) && (xpassword == null) && (req.body.token != null)) { xusername = req.session.tokenusername; xpassword = req.session.tokenpassword; }
|
||||
|
||||
// Authenticate the user
|
||||
obj.authenticate(xusername, xpassword, domain, function (err, userid, passhint) {
|
||||
obj.authenticate(xusername, xpassword, domain, function (err, userid, passhint, loginOptions) {
|
||||
if (userid) {
|
||||
var user = obj.users[userid];
|
||||
|
||||
@ -952,7 +955,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
var push2fa = ((parent.firebase != null) && (user.otpdev != null));
|
||||
|
||||
// Check if this user has 2-step login active
|
||||
if ((req.session.loginmode != '6') && checkUserOneTimePasswordRequired(domain, user, req)) {
|
||||
if ((req.session.loginmode != '6') && checkUserOneTimePasswordRequired(domain, user, req, loginOptions)) {
|
||||
if ((req.body.hwtoken == '**email**') && email2fa) {
|
||||
user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
|
||||
obj.db.SetUser(user);
|
||||
@ -1060,7 +1063,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
// Login successful
|
||||
if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
|
||||
parent.debug('web', 'handleLoginRequest: successful 2FA login');
|
||||
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct);
|
||||
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
|
||||
}
|
||||
});
|
||||
return;
|
||||
@ -1081,7 +1084,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
// Login successful
|
||||
if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
|
||||
parent.debug('web', 'handleLoginRequest: successful login');
|
||||
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct);
|
||||
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
|
||||
} else {
|
||||
// Login failed, log the error
|
||||
if (obj.parent.authlog) { obj.parent.authLog('https', 'Failed password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
|
||||
@ -1120,7 +1123,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
});
|
||||
}
|
||||
|
||||
function completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct) {
|
||||
function completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions) {
|
||||
// Check if we need to change the password
|
||||
if ((typeof user.passchange == 'number') && ((user.passchange == -1) || ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.reset == 'number') && (user.passchange + (domain.passwordrequirements.reset * 86400) < Math.floor(Date.now() / 1000))))) {
|
||||
// Request a password change
|
||||
@ -1144,6 +1147,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
|
||||
const ua = getUserAgentInfo(req);
|
||||
const loginEvent = { etype: 'user', userid: user._id, username: user.name, account: obj.CloneSafeUser(user), action: 'login', msgid: 107, msgArgs: [req.clientIp, ua.browserStr, ua.osStr], msg: 'Account login', domain: domain.id, ip: req.clientIp, userAgent: req.headers['user-agent'] };
|
||||
if ((loginOptions != null) && (loginOptions.tokenName != null) && (loginOptions.tokenUser != null)) { loginEvent.tokenName = loginOptions.tokenName; loginEvent.tokenUser = loginOptions.tokenUser; } // If a login token was used, add it to the event.
|
||||
obj.parent.DispatchEvent(targets, obj, loginEvent);
|
||||
|
||||
// Regenerate session when signing in to prevent fixation
|
||||
@ -1164,6 +1168,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
req.session.domainid = domain.id;
|
||||
req.session.currentNode = '';
|
||||
req.session.ip = req.clientIp;
|
||||
|
||||
// If a login token was used, add this information and expire time to the session.
|
||||
if ((loginOptions != null) && (loginOptions.tokenName != null) && (loginOptions.tokenUser != null)) {
|
||||
req.session.loginToken = loginOptions.tokenUser;
|
||||
if (loginOptions.expire != null) { req.session.expire = loginOptions.expire; }
|
||||
}
|
||||
|
||||
if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
|
||||
if (req.body.host) {
|
||||
// TODO: This is a terrible search!!! FIX THIS.
|
||||
@ -1371,7 +1382,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
}
|
||||
|
||||
// Authenticate the user
|
||||
obj.authenticate(req.session.resettokenusername, req.session.resettokenpassword, domain, function (err, userid, passhint) {
|
||||
obj.authenticate(req.session.resettokenusername, req.session.resettokenpassword, domain, function (err, userid, passhint, loginOptions) {
|
||||
if (userid) {
|
||||
// Login
|
||||
var user = obj.users[userid];
|
||||
@ -1428,7 +1439,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
req.session.userid = userid;
|
||||
req.session.domainid = domain.id;
|
||||
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
|
||||
completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tokenusername, req.session.tokenpassword, direct);
|
||||
completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tokenusername, req.session.tokenpassword, direct, loginOptions);
|
||||
}, 0);
|
||||
}
|
||||
}, 0);
|
||||
@ -1989,7 +2000,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) { parent.debug('web', 'handleDeleteAccountRequest: account settings locked.'); res.sendStatus(404); return; }
|
||||
|
||||
// Check if the password is correct
|
||||
obj.authenticate(user._id.split('/')[2], req.body.apassword1, domain, function (err, userid) {
|
||||
obj.authenticate(user._id.split('/')[2], req.body.apassword1, domain, function (err, userid, passhint, loginOptions) {
|
||||
var deluser = obj.users[userid];
|
||||
if ((userid != null) && (deluser != null)) {
|
||||
// Remove all links to this user
|
||||
@ -2336,7 +2347,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
});
|
||||
} else if (req.query.user && req.query.pass) {
|
||||
// User credentials are being passed in the URL. WARNING: Putting credentials in a URL is bad security... but people are requesting this option.
|
||||
obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) {
|
||||
obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid, passhint, loginOptions) {
|
||||
if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + req.connection.user + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); }
|
||||
parent.debug('web', 'handleRootRequest: user/pass in URL auth ok.');
|
||||
req.session.userid = userid;
|
||||
@ -5975,7 +5986,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
case 'userAuth': { // This command is used to perform user authentication.
|
||||
// Check username and password authentication
|
||||
if ((typeof command.username == 'string') && (typeof command.password == 'string')) {
|
||||
obj.authenticate(Buffer.from(command.username, 'base64').toString(), Buffer.from(command.password, 'base64').toString(), domain, function (err, userid) {
|
||||
obj.authenticate(Buffer.from(command.username, 'base64').toString(), Buffer.from(command.password, 'base64').toString(), domain, function (err, userid, passhint, loginOptions) {
|
||||
var user = obj.users[userid];
|
||||
if ((err == null) && (user)) {
|
||||
// Check if a 2nd factor is needed
|
||||
@ -5985,7 +5996,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
var twoFactorCookieDays = 30;
|
||||
if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; }
|
||||
|
||||
if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
|
||||
if (checkUserOneTimePasswordRequired(domain, user, req, loginOptions) == true) {
|
||||
// Figure out if email 2FA is allowed
|
||||
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null) && (user.otpekey != null));
|
||||
var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
|
||||
@ -6109,7 +6120,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
// A web socket session can be authenticated in many ways (Default user, session, user/pass and cookie). Check authentication here.
|
||||
if ((req.query.user != null) && (req.query.pass != null)) {
|
||||
// A user/pass is provided in URL arguments
|
||||
obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid) {
|
||||
obj.authenticate(req.query.user, req.query.pass, domain, function (err, userid, passhint, loginOptions) {
|
||||
|
||||
// See if we support two-factor trusted cookies
|
||||
var twoFactorCookieDays = 30;
|
||||
@ -6118,7 +6129,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
var user = obj.users[userid];
|
||||
if ((err == null) && (user)) {
|
||||
// Check if a 2nd factor is needed
|
||||
if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
|
||||
if (checkUserOneTimePasswordRequired(domain, user, req, loginOptions) == true) {
|
||||
// Figure out if email 2FA is allowed
|
||||
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null) && (user.otpekey != null));
|
||||
var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
|
||||
@ -6224,11 +6235,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
var s = req.headers['x-meshauth'].split(',');
|
||||
for (var i in s) { s[i] = Buffer.from(s[i], 'base64').toString(); }
|
||||
if ((s.length < 2) || (s.length > 3)) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'noauth-2c' })); ws.close(); } catch (e) { } return; }
|
||||
obj.authenticate(s[0], s[1], domain, function (err, userid) {
|
||||
obj.authenticate(s[0], s[1], domain, function (err, userid, passhint, loginOptions) {
|
||||
var user = obj.users[userid];
|
||||
if ((err == null) && (user)) {
|
||||
// Check if a 2nd factor is needed
|
||||
if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
|
||||
if (checkUserOneTimePasswordRequired(domain, user, req, loginOptions) == true) {
|
||||
|
||||
// See if we support two-factor trusted cookies
|
||||
var twoFactorCookieDays = 30;
|
||||
|
Loading…
Reference in New Issue
Block a user