Added 2FA to user login reports

This commit is contained in:
Ylian Saint-Hilaire 2022-03-04 15:56:19 -08:00
parent 0eadd5ed67
commit ecc27f5f20
3 changed files with 84 additions and 35 deletions

View File

@ -5931,7 +5931,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
break; break;
} }
case 3: { case 3: {
userLoginReport(command, msgIdFilter); userLoginReport(command);
break; break;
} }
} }
@ -6988,9 +6988,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var data = { groups: {} }; var data = { groups: {} };
if (command.groupBy == 1) { if (command.groupBy == 1) {
data.groupFormat = 'user'; data.groupFormat = 'user';
data.columns = [{ id: 'time', title: "time", format: 'datetime' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }]; data.columns = [{ id: 'time', title: "time", format: 'datetime' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }, { id: 'twofactor', title: "twofactor", format: '2fa' }];
} else if (command.groupBy == 3) { } else if (command.groupBy == 3) {
data.columns = [{ id: 'time', title: "time", format: 'time' }, { id: 'userid', title: "user", format: 'user' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }]; data.columns = [{ id: 'time', title: "time", format: 'time' }, { id: 'userid', title: "user", format: 'user' }, { id: 'ip', title: "ip" }, { id: 'browser', title: "browser" }, { id: 'os', title: "os" }, { id: 'twofactor', title: "twofactor", format: '2fa' }];
} }
if (showInvalidLoginAttempts) { data.columns.push({ id: 'msg', title: "msg", format: 'msg' }); } if (showInvalidLoginAttempts) { data.columns.push({ id: 'msg', title: "msg", format: 'msg' }); }
@ -7002,7 +7002,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (command.groupBy == 1) { // Add entry per user if (command.groupBy == 1) { // Add entry per user
if (data.groups[docs[i].userid] == null) { data.groups[docs[i].userid] = { entries: [] }; } if (data.groups[docs[i].userid] == null) { data.groups[docs[i].userid] = { entries: [] }; }
const entry = { time: docs[i].time.valueOf(), ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2] }; const entry = { time: docs[i].time.valueOf(), ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2], twofactor: docs[i].twoFactorType ? docs[i].twoFactorType : '' };
if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid } if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid }
data.groups[docs[i].userid].entries.push(entry); data.groups[docs[i].userid].entries.push(entry);
} else if (command.groupBy == 3) { // Add entry per day } else if (command.groupBy == 3) { // Add entry per day
@ -7013,7 +7013,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
day = docs[i].time; // TODO day = docs[i].time; // TODO
} }
if (data.groups[day] == null) { data.groups[day] = { entries: [] }; } if (data.groups[day] == null) { data.groups[day] = { entries: [] }; }
const entry = { time: docs[i].time.valueOf(), userid: docs[i].userid, ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2] }; const entry = { time: docs[i].time.valueOf(), userid: docs[i].userid, ip: docs[i].msgArgs[0], browser: docs[i].msgArgs[1], os: docs[i].msgArgs[2], twofactor: docs[i].twoFactorType ? docs[i].twoFactorType : '' };
if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid } if (showInvalidLoginAttempts) { entry.msg = docs[i].msgid }
data.groups[day].entries.push(entry); data.groups[day].entries.push(entry);
} }

View File

@ -15701,7 +15701,7 @@
} }
function renderReport(r) { function renderReport(r) {
var colTranslation = { time: "Time", device: "Device", session: "Session", user: "User", devgroup: "Device Group", guest: "Guest", length: "Length", bytesin: "Bytes In", bytesout: "Bytes Out", os: "Operating System", ip: "IP Address", browser: "Browser", msg: "Message" } var colTranslation = { time: "Time", device: "Device", session: "Session", user: "User", devgroup: "Device Group", guest: "Guest", length: "Length", bytesin: "Bytes In", bytesout: "Bytes Out", os: "Operating System", ip: "IP Address", browser: "Browser", msg: "Message", twofactor: "2nd Factor" }
var x = '<table style=width:100%>', firstItem; var x = '<table style=width:100%>', firstItem;
var sumByCol = null; // Indicate by what colum we sum by var sumByCol = null; // Indicate by what colum we sum by
var sumByValues = []; // Indicate by what values we sum by var sumByValues = []; // Indicate by what values we sum by
@ -15861,6 +15861,18 @@
if (v == 109) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny4"></div>' + "Locked account"; if (v == 109) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny4"></div>' + "Locked account";
if (v == 110) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny3"></div>' + "Invalid login attempt"; if (v == 110) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny3"></div>' + "Invalid login attempt";
} }
if (f == '2fa') {
if (v == '') { return "None"; }
if (v == 'backup') { return "Backup Codes"; }
if (v == 'fido') { return "FIDO key"; }
if (v == 'sms') { return "SMS message"; }
if (v == 'hwotp') { return "Hardware OTP"; }
if (v == 'push') { return "Push Notification"; }
if (v == 'otp') { return "One-Time Password"; }
if (v == 'cookie') { return "Remember Device"; }
if (v == 'tokenlogin') { return "Login Token"; }
if (v == 'ipaddr') { return "IP Address"; }
}
return EscapeHtml(v); return EscapeHtml(v);
} }

View File

@ -792,6 +792,32 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
if (req.query.key != null) { res.redirect(domain.url + '?key=' + req.query.key); } else { res.redirect(domain.url); } if (req.query.key != null) { res.redirect(domain.url + '?key=' + req.query.key); } else { res.redirect(domain.url); }
} }
// Return an object with 2FA type if 2-step auth can be skipped
function checkUserOneTimePasswordSkip(domain, user, req, loginOptions) {
if (parent.config.settings.no2factorauth == true) return null;
// If this login occured using a login token, no 2FA needed.
if ((loginOptions != null) && (typeof loginOptions.tokenName === 'string')) { return { twoFactorType: 'tokenlogin' }; }
// 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 { twoFactorType: 'ipaddr' }; } }
}
// Check if a 2nd factor cookie is present
if (typeof req.headers.cookie == 'string') {
const cookies = req.headers.cookie.split('; ');
for (var i in cookies) {
if (cookies[i].startsWith('twofactor=')) {
var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(cookies[i].substring(10)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { return { twoFactorType: 'cookie' }; }
}
}
}
return null;
}
// Return true if this user has 2-step auth active // Return true if this user has 2-step auth active
function checkUserOneTimePasswordRequired(domain, user, req, loginOptions) { function checkUserOneTimePasswordRequired(domain, user, req, loginOptions) {
// If this login occured using a login token, no 2FA needed. // If this login occured using a login token, no 2FA needed.
@ -835,7 +861,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// Check 2FA login cookie // Check 2FA login cookie
if ((token != null) && (token.startsWith('cookie='))) { if ((token != null) && (token.startsWith('cookie='))) {
var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(token.substring(7)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout. var twoFactorCookie = obj.parent.decodeCookie(decodeURIComponent(token.substring(7)), obj.parent.loginCookieEncryptionKey, (30 * 24 * 60)); // If the cookies does not have an expire feild, assume 30 day timeout.
if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { func(true); return; } if ((twoFactorCookie != null) && ((obj.args.cookieipcheck === false) || (twoFactorCookie.ip == null) || (twoFactorCookie.ip === req.clientIp)) && (twoFactorCookie.userid == user._id)) { func(true, { twoFactorType: 'cookie' }); return; }
} }
// Check email key // Check email key
@ -845,7 +871,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
user.otpekey = {}; user.otpekey = {};
obj.db.SetUser(user); obj.db.SetUser(user);
parent.debug('web', 'checkUserOneTimePassword: success (email).'); parent.debug('web', 'checkUserOneTimePassword: success (email).');
func(true); func(true, { twoFactorType: 'email' });
return; return;
} }
} }
@ -857,7 +883,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
delete user.otpsms; delete user.otpsms;
obj.db.SetUser(user); obj.db.SetUser(user);
parent.debug('web', 'checkUserOneTimePassword: success (SMS).'); parent.debug('web', 'checkUserOneTimePassword: success (SMS).');
func(true); func(true, { twoFactorType: 'sms' });
return; return;
} }
} }
@ -908,7 +934,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
webAuthnKey.counter = webauthnResponse.counter; webAuthnKey.counter = webauthnResponse.counter;
obj.db.SetUser(user); obj.db.SetUser(user);
parent.debug('web', 'checkUserOneTimePassword: success (hardware).'); parent.debug('web', 'checkUserOneTimePassword: success (hardware).');
func(true); func(true, { twoFactorType: 'fido' });
} else { } else {
parent.debug('web', 'checkUserOneTimePassword: fail (hardware).'); parent.debug('web', 'checkUserOneTimePassword: fail (hardware).');
func(false); func(false);
@ -924,7 +950,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window
if (user.otpsecret && (typeof (token) == 'string') && (token.length == 6) && (otplib.authenticator.check(token, user.otpsecret) == true)) { if (user.otpsecret && (typeof (token) == 'string') && (token.length == 6) && (otplib.authenticator.check(token, user.otpsecret) == true)) {
parent.debug('web', 'checkUserOneTimePassword: success (authenticator).'); parent.debug('web', 'checkUserOneTimePassword: success (authenticator).');
func(true); func(true, { twoFactorType: 'otp' });
return; return;
}; };
@ -934,12 +960,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
for (var i = 0; i < user.otpkeys.keys.length; i++) { for (var i = 0; i < user.otpkeys.keys.length; i++) {
if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) { if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) {
parent.debug('web', 'checkUserOneTimePassword: success (one-time).'); parent.debug('web', 'checkUserOneTimePassword: success (one-time).');
user.otpkeys.keys[i].u = false; func(true); return; user.otpkeys.keys[i].u = false; func(true, { twoFactorType: 'backup' }); return;
} }
} }
} }
// Check OTP hardware key // Check OTP hardware key (Yubikey OTP)
if ((domain.yubikey != null) && (domain.yubikey.id != null) && (domain.yubikey.secret != null) && (user.otphkeys != null) && (user.otphkeys.length > 0) && (typeof (token) == 'string') && (token.length == 44)) { if ((domain.yubikey != null) && (domain.yubikey.id != null) && (domain.yubikey.secret != null) && (user.otphkeys != null) && (user.otphkeys.length > 0) && (typeof (token) == 'string') && (token.length == 44)) {
var keyId = token.substring(0, 12); var keyId = token.substring(0, 12);
@ -955,7 +981,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
yubikeyotp.verifyOTP(request, function (err, results) { yubikeyotp.verifyOTP(request, function (err, results) {
if ((results != null) && (results.status == 'OK')) { if ((results != null) && (results.status == 'OK')) {
parent.debug('web', 'checkUserOneTimePassword: success (Yubikey).'); parent.debug('web', 'checkUserOneTimePassword: success (Yubikey).');
func(true); func(true, { twoFactorType: 'hwotp' });
} else { } else {
parent.debug('web', 'checkUserOneTimePassword: fail (Yubikey).'); parent.debug('web', 'checkUserOneTimePassword: fail (Yubikey).');
func(false); func(false);
@ -1033,8 +1059,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null)); var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
var push2fa = ((parent.firebase != null) && (user.otpdev != null)); var push2fa = ((parent.firebase != null) && (user.otpdev != null));
// Check if two factor can be skipped
const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
// Check if this user has 2-step login active // Check if this user has 2-step login active
if ((req.session.loginmode != 6) && checkUserOneTimePasswordRequired(domain, user, req, loginOptions)) { if ((twoFactorSkip == null) && (req.session.loginmode != 6) && checkUserOneTimePasswordRequired(domain, user, req, loginOptions)) {
if ((req.body.hwtoken == '**timeout**')) { if ((req.body.hwtoken == '**timeout**')) {
delete req.session; // Clear the session delete req.session; // Clear the session
res.redirect(domain.url + getQueryPortion(req)); res.redirect(domain.url + getQueryPortion(req));
@ -1096,7 +1125,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
return; return;
} }
checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) { checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result, authData) {
if (result == false) { if (result == false) {
var randomWaitTime = 0; var randomWaitTime = 0;
@ -1158,6 +1187,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// Login successful // Login successful
if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } 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'); parent.debug('web', 'handleLoginRequest: successful 2FA login');
if (authData != null) { if (loginOptions == null) { loginOptions = {}; } loginOptions.twoFactorType = authData.twoFactorType; }
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions); completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
} }
}); });
@ -1179,6 +1209,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// Login successful // Login successful
if (obj.parent.authlog) { obj.parent.authLog('https', 'Accepted password for ' + xusername + ' from ' + req.clientIp + ' port ' + req.connection.remotePort); } 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'); parent.debug('web', 'handleLoginRequest: successful login');
if (twoFactorSkip != null) { if (loginOptions == null) { loginOptions = {}; } loginOptions.twoFactorType = twoFactorSkip.twoFactorType; }
completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions); completeLoginRequest(req, res, domain, user, userid, xusername, xpassword, direct, loginOptions);
} else { } else {
// Login failed, log the error // Login failed, log the error
@ -1242,7 +1273,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } } if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
const ua = getUserAgentInfo(req); 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'] }; 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. if (loginOptions != null) {
if ((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.
if (loginOptions.twoFactorType != null) { loginEvent.twoFactorType = loginOptions.twoFactorType; }
}
obj.parent.DispatchEvent(targets, obj, loginEvent); obj.parent.DispatchEvent(targets, obj, loginEvent);
// Regenerate session when signing in to prevent fixation // Regenerate session when signing in to prevent fixation
@ -1614,7 +1648,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var user = docs[i]; var user = docs[i];
if (checkUserOneTimePasswordRequired(domain, user, req) == true) { if (checkUserOneTimePasswordRequired(domain, user, req) == true) {
// Second factor setup, request it now. // Second factor setup, request it now.
checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) { checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result, authData) {
if (result == false) { if (result == false) {
if (i == 0) { if (i == 0) {
@ -5879,20 +5913,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); }); obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking. obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking.
obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); }); obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
obj.app.ws(url + 'webider.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie) { obj.meshIderHandler.CreateAmtIderSession(obj, obj.db, ws1, req1, obj.args, domain, user); }); }); obj.app.ws(url + 'webider.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie, authData) { obj.meshIderHandler.CreateAmtIderSession(obj, obj.db, ws1, req1, obj.args, domain, user); }); });
obj.app.ws(url + 'control.ashx', function (ws, req) { obj.app.ws(url + 'control.ashx', function (ws, req) {
getWebsocketArgs(ws, req, function (ws, req) { getWebsocketArgs(ws, req, function (ws, req) {
const domain = getDomain(req); const domain = getDomain(req);
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { ws.close(); return; } // Check 3FA URL key if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { ws.close(); return; } // Check 3FA URL key
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
if (user == null) { // User is not authenticated, perform inner server authentication if (user == null) { // User is not authenticated, perform inner server authentication
if (req.headers['x-meshauth'] === '*') { if (req.headers['x-meshauth'] === '*') {
PerformWSSessionInnerAuth(ws, req, domain, function (ws1, req1, domain, user) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user); }); // User is authenticated PerformWSSessionInnerAuth(ws, req, domain, function (ws1, req1, domain, user) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user, authData); }); // User is authenticated
} else { } else {
try { ws.close(); } catch (ex) { } // user is not authenticated and inner authentication was not requested, disconnect now. try { ws.close(); } catch (ex) { } // user is not authenticated and inner authentication was not requested, disconnect now.
} }
} else { } else {
obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user); // User is authenticated obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user, authData); // User is authenticated
} }
}); });
}); });
@ -5912,7 +5946,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
obj.app.get(url + 'sharing', handleSharingRequest); obj.app.get(url + 'sharing', handleSharingRequest);
obj.app.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler obj.app.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler
obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { obj.app.ws(url + 'meshrelay.ashx', function (ws, req) {
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) { if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {
obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n
} else { } else {
@ -5922,7 +5956,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
}); });
if (obj.args.wanonly != true) { // If the server is not in WAN mode, allow server relayed connections. if (obj.args.wanonly != true) { // If the server is not in WAN mode, allow server relayed connections.
obj.app.ws(url + 'localrelay.ashx', function (ws, req) { obj.app.ws(url + 'localrelay.ashx', function (ws, req) {
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
if ((user == null) || (cookie == null)) { if ((user == null) || (cookie == null)) {
try { ws1.close(); } catch (ex) { } try { ws1.close(); } catch (ex) { }
} else { } else {
@ -5976,12 +6010,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
} catch (ex) { console.log(ex); } } catch (ex) { console.log(ex); }
}); });
obj.app.ws(url + 'sshterminalrelay.ashx', function (ws, req) { obj.app.ws(url + 'sshterminalrelay.ashx', function (ws, req) {
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
require('./apprelays.js').CreateSshTerminalRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args); require('./apprelays.js').CreateSshTerminalRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args);
}); });
}); });
obj.app.ws(url + 'sshfilesrelay.ashx', function (ws, req) { obj.app.ws(url + 'sshfilesrelay.ashx', function (ws, req) {
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
require('./apprelays.js').CreateSshFilesRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args); require('./apprelays.js').CreateSshFilesRelay(obj, obj.db, ws1, req1, domain, user, cookie, obj.args);
}); });
}); });
@ -6364,7 +6398,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// Setup mesh relay on alternative agent-only port // Setup mesh relay on alternative agent-only port
obj.agentapp.ws(url + 'meshrelay.ashx', function (ws, req) { obj.agentapp.ws(url + 'meshrelay.ashx', function (ws, req) {
PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie, authData) {
if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) { if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) {
obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n obj.meshDesktopMultiplexHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); // Desktop multiplexor 1-to-n
} else { } else {
@ -6478,7 +6512,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var twoFactorCookieDays = 30; var twoFactorCookieDays = 30;
if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; } if (typeof domain.twofactorcookiedurationdays == 'number') { twoFactorCookieDays = domain.twofactorcookiedurationdays; }
if (checkUserOneTimePasswordRequired(domain, user, req, loginOptions) == true) { // Check if two factor can be skipped
const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
if ((twoFactorSkip == null) && (checkUserOneTimePasswordRequired(domain, user, req, loginOptions) == true)) {
// Figure out if email 2FA is allowed // Figure out if email 2FA is allowed
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null) && (user.otpekey != null)); 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)); var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
@ -6517,7 +6554,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, sms2fa: sms2fa, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (ex) { console.log(ex); } try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, sms2fa: sms2fa, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (ex) { console.log(ex); }
} }
} else { } else {
checkUserOneTimePassword(req, domain, user, command.token, null, function (result) { checkUserOneTimePassword(req, domain, user, command.token, null, function (result, authData) {
if (result == false) { if (result == false) {
// Failed, ask for a login token again // Failed, ask for a login token again
parent.debug('web', 'Invalid login token, asking again'); parent.debug('web', 'Invalid login token, asking again');
@ -6532,7 +6569,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// We are authenticated // We are authenticated
ws._socket.pause(); ws._socket.pause();
ws.removeAllListeners(['message', 'close', 'error']); ws.removeAllListeners(['message', 'close', 'error']);
func(ws, req, domain, user); func(ws, req, domain, user, authData);
} }
} }
}); });
@ -6548,7 +6585,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// We are authenticated // We are authenticated
ws._socket.pause(); ws._socket.pause();
ws.removeAllListeners(['message', 'close', 'error']); ws.removeAllListeners(['message', 'close', 'error']);
func(ws, req, domain, user); func(ws, req, domain, user, twoFactorSkip);
} }
} }
} }
@ -6664,7 +6701,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, sms2fa: sms2fa, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (e) { } try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, sms2fa: sms2fa, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (e) { }
} }
} else { } else {
checkUserOneTimePassword(req, domain, user, req.query.token, null, function (result) { checkUserOneTimePassword(req, domain, user, req.query.token, null, function (result, authData) {
if (result == false) { if (result == false) {
// Failed, ask for a login token again // Failed, ask for a login token again
parent.debug('web', 'Invalid login token, asking again'); parent.debug('web', 'Invalid login token, asking again');
@ -6676,7 +6713,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
parent.debug('web', 'Invalid login, asking for email validation'); parent.debug('web', 'Invalid login, asking for email validation');
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, email2fasent: true })); ws.close(); } catch (e) { } try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, sms2fa: sms2fa, email2fasent: true })); ws.close(); } catch (e) { }
} else { } else {
func(ws, req, domain, user); func(ws, req, domain, user, null, authData);
} }
} }
}); });
@ -6761,7 +6798,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
if (s.length != 3) { if (s.length != 3) {
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, sms2fa: sms2fa, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (e) { } try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth', msg: 'tokenrequired', email2fa: email2fa, sms2fa: sms2fa, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (e) { }
} else { } else {
checkUserOneTimePassword(req, domain, user, s[2], null, function (result) { checkUserOneTimePassword(req, domain, user, s[2], null, function (result, authData) {
if (result == false) { if (result == false) {
if ((s[2] == '**email**') && (email2fa == true)) { if ((s[2] == '**email**') && (email2fa == true)) {
// Cause a token to be sent to the user's registered email // Cause a token to be sent to the user's registered email
@ -6790,7 +6827,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
parent.debug('web', 'Invalid login, asking for email validation'); parent.debug('web', 'Invalid login, asking for email validation');
try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (e) { } try { ws.send(JSON.stringify({ action: 'close', cause: 'emailvalidation', msg: 'emailvalidationrequired', email2fa: email2fa, email2fasent: true, twoFactorCookieDays: twoFactorCookieDays })); ws.close(); } catch (e) { }
} else { } else {
func(ws, req, domain, user); func(ws, req, domain, user, null, authData);
} }
} }
}); });