Added 2FA timeout on login screen, default is 5 minutes.

Ylian Saint-Hilaire committed Jul 24, 2021 at 15:14 UTC 21aabc676d2358968beaddb20f9af0408eba0e4f
6 files changed +27 -3
meshcentral-config-schema.json
+2 -1
@@ -384,7 +384,8 @@
384 "skip2factor": { "type": "string", "description": "IP addresses where 2FA login is skipped, for example: 127.0.0.1,192.168.2.0/24" },
385 "oldPasswordBan": { "type": "integer", "description": "Number of old passwords the server should remember and not allow the user to switch back to." },
386 "banCommonPasswords": { "type": "boolean", "default": false, "description": "Uses WildLeek to block use of the 10000 most commonly used passwords." },
387 - "loginTokens": { "type": "boolean", "default": true, "description": "Allows users to create alternative username/passwords for their account." }
387 + "loginTokens": { "type": "boolean", "default": true, "description": "Allows users to create alternative username/passwords for their account." },
388 + "twoFactorTimeout": { "type": "integer", "default": 300, "description": "Maximum about of time the to wait for a 2FA token on the login page in seconds." }
389 }
390 },
391 "twoFactorCookieDurationDays": { "type": "integer", "default": 30, "description": "Number of days that a user is allowed to remember this device for when completing 2FA. Set this to 0 to remove this option." },
sample-config-advanced.json
+2 -1
@@ -196,7 +196,8 @@
196 "force2factor": true,
197 "skip2factor": "127.0.0.1,192.168.2.0/24",
198 "oldPasswordBan": 5,
199 - "banCommonPasswords": false
199 + "banCommonPasswords": false,
200 + "twoFactorTimeout": 300
201 },
202 "_twoFactorCookieDurationDays": 30,
203 "_agentInviteCodes": true,
views/login-mobile.handlebars
+3
@@ -326,6 +326,7 @@
326 var otpsms = ('{{{otpsms}}}' === 'true');
327 var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
328 var authStrategies = '{{{authStrategies}}}'.split(',');
329 + var tokenTimeout = parseInt('{{{tokenTimeout}}}');
330
331 // Display the right server message
332 var messageid = parseInt('{{{messageid}}}');
@@ -402,6 +403,7 @@
403 QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
404
405 if (loginMode == '4') {
406 + if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
407 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
408 QV('securityKeyButton', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'));
409 QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4));
@@ -409,6 +411,7 @@
411 }
412
413 if (loginMode == '5') {
414 + if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
415 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
416 if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
417 if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
views/login.handlebars
+3
@@ -325,6 +325,7 @@
325 var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
326 var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
327 var authStrategies = '{{{authStrategies}}}'.split(',');
328 + var tokenTimeout = parseInt('{{{tokenTimeout}}}');
329
330 function startup() {
331 // Display the right server message
@@ -432,6 +433,7 @@
433 QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
434
435 if (loginMode == '4') {
436 + if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
437 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
438 QV('securityKeyButton', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'));
439 QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4));
@@ -439,6 +441,7 @@
441 }
442
443 if (loginMode == '5') {
444 + if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
445 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
446 QV('securityKeyButton2', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'));
447 QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4));
views/login2.handlebars
+3
@@ -358,6 +358,7 @@
358 var otppush = (decodeURIComponent('{{{otppush}}}') === 'true');
359 var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
360 var authStrategies = '{{{authStrategies}}}'.split(',');
361 + var tokenTimeout = parseInt('{{{tokenTimeout}}}');
362 var websocket = null;
363
364 function startup() {
@@ -463,6 +464,7 @@
464 QV('hrAccountDiv', (emailCheck == 'true') || (newAccountPass == 1));
465
466 if (loginMode == '4') {
467 + if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
468 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
469 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
470 var emailkey = otpemail && (messageid != 2) && (messageid != 4);
@@ -476,6 +478,7 @@
478 }
479
480 if (loginMode == '5') {
481 + if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); }
482 try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
483 var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn');
484 var emailkey = otpemail && (messageid != 2) && (messageid != 4);
webserver.js
+14 -1
@@ -1018,6 +1018,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1018
1019 // Check if this user has 2-step login active
1020 if ((req.session.loginmode != 6) && checkUserOneTimePasswordRequired(domain, user, req, loginOptions)) {
1021 + if ((req.body.hwtoken == '**timeout**')) {
1022 + delete req.session; // Clear the session
1023 + res.redirect(domain.url + getQueryPortion(req));
1024 + return;
1025 + }
1026 +
1027 if ((req.body.hwtoken == '**email**') && email2fa) {
1028 user.otpekey = { k: obj.common.zeroPad(getRandomEightDigitInteger(), 8), d: Date.now() };
1029 obj.db.SetUser(user);
@@ -2879,6 +2885,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2885 var customui = '';
2886 if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
2887
2888 + // Get two-factor screen timeout
2889 + var twoFactorTimeout = 300000; // Default is 5 minutes, 0 for no timeout.
2890 + if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.twofactortimeout == 'number')) {
2891 + twoFactorTimeout = domain.passwordrequirements.twofactortimeout * 1000;
2892 + }
2893 +
2894 // Render the login page
2895 render(req, res,
2896 getRenderPage((domain.sitestyle == 2) ? 'login2' : 'login', req, domain),
@@ -2907,7 +2919,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2919 otppush: otppush,
2920 twoFactorCookieDays: twoFactorCookieDays,
2921 authStrategies: authStrategies.join(','),
2910 - loginpicture: (typeof domain.loginpicture == 'string')
2922 + loginpicture: (typeof domain.loginpicture == 'string'),
2923 + tokenTimeout: twoFactorTimeout // Two-factor authentication screen timeout in milliseconds
2924 }, req, domain, (domain.sitestyle == 2) ? 'login2' : 'login'));
2925 }
2926