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('*'); }