diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index 8e1c7f3c..3c9603a4 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -455,7 +455,7 @@ "skip2factor": { "type": "string", "description": "IP addresses where 2FA login is skipped, for example: 127.0.0.1,192.168.2.0/24" }, "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." }, + "loginTokens": { "type": [ "boolean", "array" ], "default": true, "description": "Allows users to create alternative username/passwords for their account. Set to false to disallow all users, or set to a userid array to only all some users." }, "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." }, "maxfidokeys": { "type": "integer", "default": null, "description": "Maximum number of FIDO/YubikeyOTP hardware 2FA keys that can be setup in a user account." }, diff --git a/meshcentral.js b/meshcentral.js index 4767c77b..7e25f917 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -1275,6 +1275,17 @@ function CreateMeshCentralServer(config, args) { } else { delete obj.config.domains[i].passwordrequirements.skip2factor; } + // Fix the list of users to add "user/domain/" if needed + if (Array.isArray(obj.config.domains[i].passwordrequirements.logintokens)) { + var newValues = []; + for (var j in obj.config.domains[i].passwordrequirements.logintokens) { + var splitVal = obj.config.domains[i].passwordrequirements.logintokens[j].split('/');; + if (splitVal.length == 1) { newValues.push('user/' + i + '/' + splitVal[0]); } + if (splitVal.length == 2) { newValues.push('user/' + splitVal[0] + '/' + splitVal[1]); } + if (splitVal.length == 3) { newValues.push(splitVal[0] + '/' + splitVal[1] + '/' + splitVal[2]); } + } + obj.config.domains[i].passwordrequirements.logintokens = newValues; + } } if ((obj.config.domains[i].auth == 'ldap') && (typeof obj.config.domains[i].ldapoptions != 'object')) { if (i == '') { console.log("ERROR: Default domain is LDAP, but is missing LDAPOptions."); } else { console.log("ERROR: Domain '" + i + "' is LDAP, but is missing LDAPOptions."); } diff --git a/meshuser.js b/meshuser.js index c1c95cbe..fe77c374 100644 --- a/meshuser.js +++ b/meshuser.js @@ -4521,7 +4521,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var err = null; if (req.session.loginToken != null) { err = "Access denied"; } // Do not allow this command when logged in using a login token - else if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens == false)) { err = "Not supported"; } // Login tokens are not supported on this server + else if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.logintokens === false)) { err = "Not supported"; } // Login tokens are not supported on this server + else if ((typeof domain.passwordrequirements == 'object') && Array.isArray(domain.passwordrequirements.logintokens) && (domain.passwordrequirements.logintokens.indexOf(user._id) < 0)) { err = "Not supported"; } // Login tokens are not supported by this user else if (common.validateString(command.name, 1, 100) == false) { err = "Invalid name"; } // Check name else if ((typeof command.expire != 'number') || (command.expire < 0)) { err = "Invalid expire value"; } // Check expire diff --git a/webserver.js b/webserver.js index fde45b0e..2147e12d 100644 --- a/webserver.js +++ b/webserver.js @@ -2941,7 +2941,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF if (((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true))) { features2 += 0x00000010; } // No agent update if (parent.amtProvisioningServer != null) { features2 += 0x00000020; } // Intel AMT LAN provisioning server if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.push2factor != false)) && (obj.parent.firebase != null)) { features2 += 0x00000040; } // Indicates device push notification 2FA is enabled - if ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.logintokens != false)) { features2 += 0x00000080; } // Indicates login tokens are allowed + if ((typeof domain.passwordrequirements != 'object') || ((domain.passwordrequirements.logintokens !== false) && ((Array.isArray(domain.passwordrequirements.logintokens) == false) || (domain.passwordrequirements.logintokens.indexOf(user._id) >= 0)))) { features2 += 0x00000080; } // Indicates login tokens are allowed if (req.session.loginToken != null) { features2 += 0x00000100; } // LoginToken mode, no account changes. if (domain.ssh == true) { features2 += 0x00000200; } // SSH is enabled if (domain.localsessionrecording === false) { features2 += 0x00000400; } // Disable local recording feature