Added 2FA lock feature.

Ylian Saint-Hilaire committed Feb 1, 2022 at 09:11 UTC 8e8192a8cd35b34f8f1973f36c4f14d6b94a7ff9
5 files changed +80 -12
meshcentral-config-schema.json
+1
@@ -442,6 +442,7 @@
442 "sms2factor": { "type": "boolean", "default": true, "description": "Set to false to disable SMS 2FA." },
443 "push2factor": { "type": "boolean", "default": true, "description": "Set to false to disable push notification 2FA." },
444 "otp2factor": { "type": "boolean", "default": true, "description": "Set to false to disable one-time-password 2FA." },
445 + "lock2factor": { "type": "boolean", "default": false, "description": "When set to true, prevents any changes to 2FA." },
446 "force2factor": { "type": "boolean", "default": false, "description": "Requires that all accounts setup 2FA." },
447 "skip2factor": { "type": "string", "description": "IP addresses where 2FA login is skipped, for example: 127.0.0.1,192.168.2.0/24" },
448 "oldPasswordBan": { "type": "integer", "description": "Number of old passwords the server should remember and not allow the user to switch back to." },
meshuser.js
+58
@@ -548,6 +548,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
548 if (parent.parent.webpush != null) { serverinfo.vapidpublickey = parent.parent.webpush.vapidPublicKey; } // Web push public key
549 if (parent.parent.amtProvisioningServer != null) { serverinfo.amtProvServerMeshId = parent.parent.amtProvisioningServer.meshid; } // Device group that allows for bare-metal Intel AMT activation
550 if ((typeof domain.autoremoveinactivedevices == 'number') && (domain.autoremoveinactivedevices > 0)) { serverinfo.autoremoveinactivedevices = domain.autoremoveinactivedevices; } // Default number of days before inactive devices are removed
551 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) { serverinfo.lock2factor = true; } // Indicate 2FA change are not allowed
552
553 // Build the mobile agent URL, this is used to connect mobile devices
554 var agentServerName = parent.getWebServerName(domain);
@@ -3146,6 +3147,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3147 }
3148 case 'otpemail':
3149 {
3150 + // Do not allow this command if 2FA's are locked
3151 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3152 +
3153 // Do not allow this command when logged in using a login token
3154 if (req.session.loginToken != null) break;
3155
@@ -3173,6 +3177,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3177 }
3178 case 'otpauth-request':
3179 {
3180 + // Do not allow this command if 2FA's are locked
3181 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3182 +
3183 // Do not allow this command when logged in using a login token
3184 if (req.session.loginToken != null) break;
3185
@@ -3198,6 +3205,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3205 }
3206 case 'otpauth-setup':
3207 {
3208 + // Do not allow this command if 2FA's are locked
3209 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3210 +
3211 // Do not allow this command when logged in using a login token
3212 if (req.session.loginToken != null) break;
3213
@@ -3234,6 +3244,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3244 }
3245 case 'otpauth-clear':
3246 {
3247 + // Do not allow this command if 2FA's are locked
3248 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3249 +
3250 // Do not allow this command when logged in using a login token
3251 if (req.session.loginToken != null) break;
3252
@@ -3262,6 +3275,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3275 }
3276 case 'otpauth-getpasswords':
3277 {
3278 + // Do not allow this command if 2FA's are locked
3279 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3280 +
3281 // Do not allow this command when logged in using a login token
3282 if (req.session.loginToken != null) break;
3283
@@ -3310,6 +3326,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3326 }
3327 case 'otp-hkey-get':
3328 {
3329 + // Do not allow this command if 2FA's are locked
3330 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3331 +
3332 // Do not allow this command when logged in using a login token
3333 if (req.session.loginToken != null) break;
3334
@@ -3326,6 +3345,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3345 }
3346 case 'otp-hkey-remove':
3347 {
3348 + // Do not allow this command if 2FA's are locked
3349 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3350 +
3351 // Do not allow this command when logged in using a login token
3352 if (req.session.loginToken != null) break;
3353
@@ -3353,6 +3375,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3375 }
3376 case 'otp-hkey-yubikey-add':
3377 {
3378 + // Do not allow this command if 2FA's are locked
3379 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3380 +
3381 // Do not allow this command when logged in using a login token
3382 if (req.session.loginToken != null) break;
3383
@@ -3411,6 +3436,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3436 }
3437 case 'otpdev-clear':
3438 {
3439 + // Do not allow this command if 2FA's are locked
3440 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3441 +
3442 // Do not allow this command when logged in using a login token
3443 if (req.session.loginToken != null) break;
3444
@@ -3431,6 +3459,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3459 }
3460 case 'otpdev-set':
3461 {
3462 + // Do not allow this command if 2FA's are locked
3463 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3464 +
3465 // Do not allow this command when logged in using a login token
3466 if (req.session.loginToken != null) break;
3467
@@ -3460,6 +3491,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3491 }
3492 case 'webauthn-startregister':
3493 {
3494 + // Do not allow this command if 2FA's are locked
3495 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3496 +
3497 // Do not allow this command when logged in using a login token
3498 if (req.session.loginToken != null) break;
3499
@@ -3477,6 +3511,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3511 }
3512 case 'webauthn-endregister':
3513 {
3514 + // Do not allow this command if 2FA's are locked
3515 + if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
3516 +
3517 // Do not allow this command when logged in using a login token
3518 if (req.session.loginToken != null) break;
3519
@@ -4775,6 +4812,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
4812 };
4813
4814 const serverUserCommands = {
4815 + '2falock': [serverUserCommand2faLock, "Shows and changes the 2FA lock state"],
4816 'acceleratorsstats': [serverUserCommandAcceleratorsStats, "Show data on work being offloaded to other CPU's"],
4817 'agentissues': [serverUserCommandAgentIssues, ""],
4818 'agentstats': [serverUserCommandAgentStats, ""],
@@ -6387,6 +6425,26 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
6425 }
6426 }
6427
6428 + function serverUserCommand2faLock(cmdData) {
6429 + var arg = null;
6430 + if (cmdData.cmdargs['_'].length > 0) { arg = cmdData.cmdargs['_'][0]; }
6431 + if (domain.passwordrequirements == null) { domain.passwordrequirements = {}; }
6432 + if (arg == 'set') {
6433 + // TODO: Change 2FA lock for peer servers
6434 + domain.passwordrequirements.lock2factor = true;
6435 + cmdData.result = "2FA lock is set";
6436 + parent.parent.DispatchEvent(['server-allusers'], obj, { action: 'serverinfochange', lock2factor: true, nolog: 1, domain: domain.id });
6437 + } else if (arg == 'clear') {
6438 + // TODO: Change 2FA lock for peer servers
6439 + delete domain.passwordrequirements.lock2factor;
6440 + cmdData.result = "2FA lock is cleared";
6441 + parent.parent.DispatchEvent(['server-allusers'], obj, { action: 'serverinfochange', lock2factor: false, nolog: 1, domain: domain.id });
6442 + } else {
6443 + cmdData.result = (domain.passwordrequirements.lock2factor == true) ? "2FA lock is set" : "2FA lock is cleared";
6444 + cmdData.result += ", use '2falock [set/clear]' to change the lock state."
6445 + }
6446 + }
6447 +
6448 function serverUserCommandAcceleratorsStats(cmdData) {
6449 var stats = parent.parent.certificateOperations.getAcceleratorStats();
6450 for (var i in stats) {
views/default-mobile.handlebars
+9 -5
@@ -1299,9 +1299,9 @@
1299 applyDesktopSettings();
1300
1301 // Arrange the user interface
1302 - QV('manageEmail2FA', features & 0x00800000);
1303 - QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000));
1304 - QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000));
1302 + QV('manageEmail2FA', (features & 0x00800000) && (serverinfo.lock2factor != true));
1303 + QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000) && (serverinfo.lock2factor != true));
1304 + QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000) && (serverinfo.lock2factor != true));
1305
1306 //attemptWebRTC = false; // For now, default WebRTC off unless we set it in the URL.
1307 if (args.webrtc != null) { attemptWebRTC = (args.webrtc == 1); }
@@ -1371,8 +1371,8 @@
1371 QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0) && (accountSettingsLocked == false)); // Hide Account Security if in single user mode or domain authentication, 2 factor auth not supported.
1372 QV('p2AccountImage', !accountSettingsLocked);
1373 QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
1374 - QV('manageAuthApp', (features & 4096) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
1375 - QV('manageOtp', (features & 4096) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0)));
1374 + QV('manageAuthApp', (serverinfo.lock2factor != true) && (features & 4096) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
1375 + QV('manageOtp', (serverinfo.lock2factor != true) && (features & 4096) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0)));
1376 QV('authPhoneNumberCheck', (userinfo.phone != null));
1377 QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
1378 QV('authAppSetupCheck', userinfo.otpsecret == 1);
@@ -1685,6 +1685,10 @@
1685 */
1686 if (message.event.noact) break; // Take no action on this event
1687 switch (message.event.action) {
1688 + case 'serverinfochange': {
1689 + if (message.event.lock2factor != null) { serverinfo.lock2factor = message.event.lock2factor; updateSelf(); }
1690 + break;
1691 + }
1692 case 'userWebState': {
1693 // New user web state, update the web page as needed
1694 if (localStorage != null) {
views/default.handlebars
+11 -6
@@ -2027,9 +2027,9 @@
2027 // Update account actions
2028 QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0) && (accountSettingsLocked == false)); // Hide Account Security if in single user mode or domain authentication, 2 factor auth not supported.
2029 QV('p2AccountActions', !accountSettingsLocked)
2030 - QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000));
2031 - QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000));
2032 - QV('manageEmail2FA', features & 0x00800000);
2030 + QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000) && (serverinfo.lock2factor != true));
2031 + QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000) && (serverinfo.lock2factor != true));
2032 + QV('manageEmail2FA', (features & 0x00800000) && (serverinfo.lock2factor != true));
2033 QV('p2AccountPassActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null) && (userinfo._id.split('/')[2].startsWith('~') == false)); // Hide Account Actions if in single user mode or domain authentication
2034 //QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel
2035 QV('accountCreateLoginTokenSpan', features2 & 0x00000080);
@@ -2126,15 +2126,16 @@
2126 var accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0));
2127 QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
2128 QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true) && (accountSettingsLocked == false));
2129 - QV('manageOtp', authFactorCount > 0);
2129 + QV('manageOtp', (serverinfo.lock2factor != true) && (authFactorCount > 0));
2130 QV('authPhoneNumberCheck', (userinfo.phone != null));
2131 QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
2132 QV('authAppSetupCheck', userinfo.otpsecret == 1);
2133 - QV('manageAuthApp', (userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0));
2133 + QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
2134 QV('authKeySetupCheck', userinfo.otphkeys > 0);
2135 QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0));
2136 QV('authCodesSetupCheck', userinfo.otpkeys > 0);
2137 - QV('managePushAuthDev', (features2 & 0x40) && (authFactorCount > 0));
2137 + QV('managePushAuthDev', (serverinfo.lock2factor != true) && (features2 & 0x40) && (authFactorCount > 0));
2138 + QV('manageHardwareOtp', (serverinfo.lock2factor != true));
2139 mainUpdate(4 + 128 + 4096);
2140
2141 // Check if none or at least 2 factors are enabled.
@@ -2838,6 +2839,10 @@
2839 if (message.event.noact) break; // Take no action on this event
2840
2841 switch (message.event.action) {
2842 + case 'serverinfochange': {
2843 + if (message.event.lock2factor != null) { serverinfo.lock2factor = message.event.lock2factor; updateSelf(); updateSiteAdmin(); }
2844 + break;
2845 + }
2846 case 'deviceShareUpdate': {
2847 if (message.event.nodeid != deviceSharesReq) break;
2848 deviceSharesNode = message.event.nodeid;
webserver.js
+1 -1
@@ -4059,7 +4059,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
4059 // Subscribe to all events we are allowed to receive
4060 obj.subscribe = function (userid, target) {
4061 const user = obj.users[userid];
4062 - const subscriptions = [userid, 'server-global'];
4062 + const subscriptions = [userid, 'server-allusers'];
4063 if (user.siteadmin != null) {
4064 // Allow full site administrators of users with all events rights to see all events.
4065 if ((user.siteadmin == 0xFFFFFFFF) || ((user.siteadmin & 2048) != 0)) { subscriptions.push('*'); }