From 15ddd3cd00e1cb3fbcd5fa53716743017f1906f0 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 28 Jul 2021 10:35:33 -0700 Subject: [PATCH] Added autofido2fa option in PasswordRequirements, #2952 --- meshcentral-config-schema.json | 3 ++- views/login-mobile.handlebars | 10 ++++++++-- views/login.handlebars | 13 +++++++++++-- views/login2.handlebars | 7 +++++++ webserver.js | 2 ++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index 23df31ac..e85c6806 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -385,7 +385,8 @@ "oldPasswordBan": { "type": "integer", "description": "Number of old passwords the server should remember and not allow the user to switch back to." }, "banCommonPasswords": { "type": "boolean", "default": false, "description": "Uses WildLeek to block use of the 10000 most commonly used passwords." }, "loginTokens": { "type": "boolean", "default": true, "description": "Allows users to create alternative username/passwords for their account." }, - "twoFactorTimeout": { "type": "integer", "default": 300, "description": "Maximum about of time the to wait for a 2FA token on the login page in seconds." } + "twoFactorTimeout": { "type": "integer", "default": 300, "description": "Maximum about of time the to wait for a 2FA token on the login page in seconds." }, + "autofido2fa": { "type": "boolean", "default": false, "description": "If true and user account has FIDO key setup, 2FA login screen will automatically request FIDO 2FA." } } }, "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." }, diff --git a/views/login-mobile.handlebars b/views/login-mobile.handlebars index 8ce0cbef..e11f9f7b 100644 --- a/views/login-mobile.handlebars +++ b/views/login-mobile.handlebars @@ -324,6 +324,7 @@ var currentpanel = 0; var otpemail = ('{{{otpemail}}}' === 'true'); var otpsms = ('{{{otpsms}}}' === 'true'); + var autofido = (decodeURIComponent('{{{autofido}}}') === 'true'); var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}'); var authStrategies = '{{{authStrategies}}}'.split(','); var tokenTimeout = parseInt('{{{tokenTimeout}}}'); @@ -405,15 +406,20 @@ if (loginMode == '4') { if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); } try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null } - QV('securityKeyButton', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')); + var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'); + QV('securityKeyButton', twofakey); QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4)); QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4)); + + // If hardware key is an option, trigger it now + if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); } } if (loginMode == '5') { if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); } try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null } - if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) { + var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'); + if (twofakey) { if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; } publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout } diff --git a/views/login.handlebars b/views/login.handlebars index bba9a00d..8fedea2f 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -323,6 +323,7 @@ var publicKeyCredentialRequestOptions = null; var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true'); var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true'); + var autofido = (decodeURIComponent('{{{autofido}}}') === 'true'); var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}'); var authStrategies = '{{{authStrategies}}}'.split(','); var tokenTimeout = parseInt('{{{tokenTimeout}}}'); @@ -435,17 +436,25 @@ if (loginMode == '4') { if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); } try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null } - QV('securityKeyButton', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')); + var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'); + QV('securityKeyButton', twofakey); QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4)); QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4)); + + // If hardware key is an option, trigger it now + if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); } } if (loginMode == '5') { if (tokenTimeout > 0) { setTimeout(function () { Q('hwtokenInput').value = '**timeout**'; QE('tokenOkButton', true); Q('tokenOkButton').click(); }, tokenTimeout); } try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null } - QV('securityKeyButton2', (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')); + var twofakey = (hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn'); + QV('securityKeyButton2', twofakey); QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4)); QV('smsKeyButton2', otpsms && (messageid != 2) && (messageid != 4)); + + // If hardware key is an option, trigger it now + if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); } } /* diff --git a/views/login2.handlebars b/views/login2.handlebars index 416cc7cb..6e23ceb9 100644 --- a/views/login2.handlebars +++ b/views/login2.handlebars @@ -356,6 +356,7 @@ var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true'); var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true'); var otppush = (decodeURIComponent('{{{otppush}}}') === 'true'); + var autofido = (decodeURIComponent('{{{autofido}}}') === 'true'); var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}'); var authStrategies = '{{{authStrategies}}}'.split(','); var tokenTimeout = parseInt('{{{tokenTimeout}}}'); @@ -475,6 +476,9 @@ QV('smsKeyButton', smskey); QV('pushKeyButton', pushkey); QV('2farow', twofakey || emailkey || smskey || pushkey); + + // If hardware key is an option, trigger it now + if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); } } if (loginMode == '5') { @@ -489,6 +493,9 @@ QV('smsKeyButton2', smskey); QV('pushKeyButton', pushkey); QV('2farow2', twofakey || emailkey || smskey || pushkey); + + // If hardware key is an option, trigger it now + if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); } } if (loginMode == '8') { diff --git a/webserver.js b/webserver.js index a1b0727d..0577d3c1 100644 --- a/webserver.js +++ b/webserver.js @@ -2863,6 +2863,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; } var otppush = (parent.firebase != null) && (req.session != null) && (req.session.tpush === 1); if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { otppush = false; } + const autofido = ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.autofido2fa == true)); // See if FIDO should be automatically prompted if user account has it. // See if we support two-factor trusted cookies var twoFactorCookieDays = 30; @@ -2917,6 +2918,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { otpemail: otpemail, otpsms: otpsms, otppush: otppush, + autofido: autofido, twoFactorCookieDays: twoFactorCookieDays, authStrategies: authStrategies.join(','), loginpicture: (typeof domain.loginpicture == 'string'),