From fc480539e1a456abc40388dfa4d4c2836b7f0257 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 7 Feb 2022 18:06:22 -0800 Subject: [PATCH] Added backupcode2factor and single2factorWarning options, #3608 --- meshcentral-config-schema.json | 2 ++ meshuser.js | 12 ++++++++++++ views/default-mobile.handlebars | 2 +- views/default.handlebars | 6 +++--- webserver.js | 2 ++ 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index 6271ee3b..cb5a4d6b 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -443,6 +443,8 @@ "sms2factor": { "type": "boolean", "default": true, "description": "Set to false to disable SMS 2FA." }, "push2factor": { "type": "boolean", "default": true, "description": "Set to false to disable push notification 2FA." }, "otp2factor": { "type": "boolean", "default": true, "description": "Set to false to disable one-time-password 2FA." }, + "backupcode2factor": { "type": "boolean", "default": true, "description": "Set to false to disable 2FA backup codes." }, + "single2factorWarning": { "type": "boolean", "default": true, "description": "Set to false to disable single 2FA warning." }, "lock2factor": { "type": "boolean", "default": false, "description": "When set to true, prevents any changes to 2FA." }, "force2factor": { "type": "boolean", "default": false, "description": "Requires that all accounts setup 2FA." }, "skip2factor": { "type": "string", "description": "IP addresses where 2FA login is skipped, for example: 127.0.0.1,192.168.2.0/24" }, diff --git a/meshuser.js b/meshuser.js index 7ba9b798..ca46da97 100644 --- a/meshuser.js +++ b/meshuser.js @@ -3183,6 +3183,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Do not allow this command if 2FA's are locked if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return; + // Do not allow this command if backup codes are not allowed + if ((domain.passwordrequirements) && (domain.passwordrequirements.backupcode2factor == false)) return; + // Do not allow this command when logged in using a login token if (req.session.loginToken != null) break; @@ -3211,6 +3214,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Do not allow this command if 2FA's are locked if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return; + // Do not allow this command if backup codes are not allowed + if ((domain.passwordrequirements) && (domain.passwordrequirements.backupcode2factor == false)) return; + // Do not allow this command when logged in using a login token if (req.session.loginToken != null) break; @@ -3250,6 +3256,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Do not allow this command if 2FA's are locked if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return; + // Do not allow this command if backup codes are not allowed + if ((domain.passwordrequirements) && (domain.passwordrequirements.backupcode2factor == false)) return; + // Do not allow this command when logged in using a login token if (req.session.loginToken != null) break; @@ -3281,6 +3290,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Do not allow this command if 2FA's are locked if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return; + // Do not allow this command if backup codes are not allowed + if ((domain.passwordrequirements) && (domain.passwordrequirements.backupcode2factor == false)) return; + // Do not allow this command when logged in using a login token if (req.session.loginToken != null) break; diff --git a/views/default-mobile.handlebars b/views/default-mobile.handlebars index 8e56f2b1..fa55e9ba 100644 --- a/views/default-mobile.handlebars +++ b/views/default-mobile.handlebars @@ -1372,7 +1372,7 @@ QV('p2AccountImage', !accountSettingsLocked); QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); QV('manageAuthApp', (serverinfo.lock2factor != true) && (features & 4096) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0))); - QV('manageOtp', (serverinfo.lock2factor != true) && (features & 4096) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0))); + QV('manageOtp', (serverinfo.lock2factor != true) && ((features2 & 0x40000) == 0) && (features & 4096) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0))); QV('authPhoneNumberCheck', (userinfo.phone != null)); QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true)); QV('authAppSetupCheck', userinfo.otpsecret == 1); diff --git a/views/default.handlebars b/views/default.handlebars index ae0664e2..4891a0a4 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -2116,7 +2116,7 @@ if (userinfo.otphkeys > 0) { authFactorCount += userinfo.otphkeys; } // FIDO hardware factor if ((features & 0x00800000) && (userinfo.otpekey == 1)) { authFactorCount++; } // EMail factor if ((features & 0x02000000) && (features & 0x04000000) && (userinfo.phone != null)) { authFactorCount++; } // SMS factor - if ((authFactorCount > 0) && (userinfo.otpkeys > 0)) { authFactorCount++; } // Backup keys + if ((authFactorCount > 0) && (userinfo.otpkeys > 0) && ((features & 0x40000) == 0)) { authFactorCount++; } // Backup keys return authFactorCount; } @@ -2126,7 +2126,7 @@ var accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0)); QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true) && (accountSettingsLocked == false)); - QV('manageOtp', (serverinfo.lock2factor != true) && (authFactorCount > 0)); + QV('manageOtp', (serverinfo.lock2factor != true) && (authFactorCount > 0) && ((features2 & 0x40000) == 0)); QV('authPhoneNumberCheck', (userinfo.phone != null)); QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true)); QV('authAppSetupCheck', userinfo.otpsecret == 1); @@ -2139,7 +2139,7 @@ mainUpdate(4 + 128 + 4096); // Check if none or at least 2 factors are enabled. - if ((backupCodesWarningDone == false) && (authFactorCount == 1)) { + if ((backupCodesWarningDone == false) && (authFactorCount == 1) && ((features2 & 0x80000) == 0)) { addNotification({ text: "Please add two-factor backup codes. If the current factor is lost, there is no way to recover this account.", title: "Two factor authentication", tag: 'backupcodes' }); backupCodesWarningDone = true; } diff --git a/webserver.js b/webserver.js index 24dd7ca1..97e13f84 100644 --- a/webserver.js +++ b/webserver.js @@ -2885,6 +2885,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF if (domain.devicesearchbarserverandclientname) { features2 += 0x00008000; } // Search bar will find both server name and client name if (domain.ipkvm) { features2 += 0x00010000; } // Indicates support for IP KVM device groups if ((domain.passwordrequirements) && (domain.passwordrequirements.otp2factor == false)) { features2 += 0x00020000; } // Indicates support for OTP 2FA is disabled + if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.backupcode2factor === false)) { features2 += 0x00040000; } // Indicates 2FA backup codes are disabled + if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.single2factorwarning === false)) { features2 += 0x00080000; } // Indicates no warning if a single 2FA is in use return { features: features, features2: features2 }; }