Added batch account addition.
This commit is contained in:
parent
4ddeca14ec
commit
27b9cfe8ea
|
@ -96,7 +96,7 @@ function CreateMeshCentralServer(config, args) {
|
|||
// Start the Meshcentral server
|
||||
obj.Start = function () {
|
||||
var i;
|
||||
try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
|
||||
try { require('./pass').hash('test', function () { }, 0); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
|
||||
|
||||
// Check for invalid arguments
|
||||
var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'memorytracking'];
|
||||
|
|
81
meshuser.js
81
meshuser.js
|
@ -952,6 +952,68 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
|
||||
// TODO: Notify all sessions on other peers.
|
||||
|
||||
break;
|
||||
}
|
||||
case 'adduserbatch':
|
||||
{
|
||||
// Add many new user accounts
|
||||
if ((user.siteadmin & 2) == 0) break;
|
||||
if (!Array.isArray(command.users)) break;
|
||||
var userCount = 0;
|
||||
for (var i in command.users) {
|
||||
if (common.validateUsername(command.users[i].user, 1, 64) == false) break; // Username is between 1 and 64 characters, no spaces
|
||||
if ((command.users[i].user == '~') || (command.users[i].user.indexOf('/') >= 0)) break; // This is a reserved user name
|
||||
if (common.validateString(command.users[i].pass, 1, 256) == false) break; // Password is between 1 and 256 characters
|
||||
if (common.checkPasswordRequirements(command.users[i].pass, domain.passwordrequirements) == false) break; // Password does not meet requirements
|
||||
if ((command.email != null) && (common.validateEmail(command.users[i].email, 1, 256) == false)) break; // Check if this is a valid email address
|
||||
userCount++;
|
||||
}
|
||||
|
||||
// Check if we exceed the maximum number of user accounts
|
||||
db.isMaxType(domain.limits.maxuseraccounts + userCount, 'user', domain.id, function (maxExceed) {
|
||||
if (maxExceed) {
|
||||
// Account count exceed, do notification
|
||||
|
||||
// Create the notification message
|
||||
var notification = { action: "msg", type: "notify", value: "Account limit reached.", title: "Server Limit", userid: user._id, username: user.name, domain: domain.id };
|
||||
|
||||
// Get the list of sessions for this user
|
||||
var sessions = parent.wssessions[user._id];
|
||||
if (sessions != null) { for (i in sessions) { try { if (sessions[i].domainid == domain.id) { sessions[i].send(JSON.stringify(notification)); } } catch (ex) { } } }
|
||||
// TODO: Notify all sessions on other peers.
|
||||
} else {
|
||||
for (var i in command.users) {
|
||||
// Check if this is an existing user
|
||||
var newuserid = 'user/' + domain.id + '/' + command.users[i].user.toLowerCase();
|
||||
var newuser = { type: 'user', _id: newuserid, name: command.users[i].user, creation: Math.floor(Date.now() / 1000), domain: domain.id };
|
||||
if (domain.newaccountsrights) { newuser.siteadmin = domain.newaccountsrights; }
|
||||
if (command.users[i].email != null) { newuser.email = command.users[i].email; } // Email
|
||||
if (command.users[i].resetNextLogin === true) { newuser.passchange = -1; } else { newuser.passchange = Math.floor(Date.now() / 1000); }
|
||||
if ((command.users[i].groups != null) && (common.validateStrArray(command.users[i].groups, 1, 32))) { newuser.groups = command.users[i].groups; } // New account are automatically part of our groups.
|
||||
|
||||
if (parent.users[newuserid] == null) {
|
||||
parent.users[newuserid] = newuser;
|
||||
|
||||
// Create a user, generate a salt and hash the password
|
||||
require('./pass').hash(command.users[i].pass, function (err, salt, hash, newuser) {
|
||||
if (err) throw err;
|
||||
newuser.salt = salt;
|
||||
newuser.hash = hash;
|
||||
db.SetUser(newuser);
|
||||
|
||||
var targets = ['*', 'server-users'];
|
||||
if (newuser.groups) { for (var i in newuser.groups) { targets.push('server-users:' + i); } }
|
||||
if (newuser.email == null) {
|
||||
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: newuser.name, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, username is ' + newuser.name, domain: domain.id });
|
||||
} else {
|
||||
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: newuser.name, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + newuser.email, domain: domain.id });
|
||||
}
|
||||
}, newuser);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case 'adduser':
|
||||
|
@ -960,6 +1022,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
if ((user.siteadmin & 2) == 0) break;
|
||||
if (common.validateUsername(command.username, 1, 64) == false) break; // Username is between 1 and 64 characters, no spaces
|
||||
if (common.validateString(command.pass, 1, 256) == false) break; // Password is between 1 and 256 characters
|
||||
if (command.username.indexOf('/') >= 0) break; // Usernames can't have '/'
|
||||
if (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements
|
||||
if ((command.email != null) && (common.validateEmail(command.email, 1, 256) == false)) break; // Check if this is a valid email address
|
||||
var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase();
|
||||
|
@ -988,7 +1051,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
parent.users[newuserid] = newuser;
|
||||
|
||||
// Create a user, generate a salt and hash the password
|
||||
require('./pass').hash(command.pass, function (err, salt, hash) {
|
||||
require('./pass').hash(command.pass, function (err, salt, hash, tag) {
|
||||
if (err) throw err;
|
||||
newuser.salt = salt;
|
||||
newuser.hash = hash;
|
||||
|
@ -996,8 +1059,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
|
||||
var targets = ['*', 'server-users'];
|
||||
if (newuser.groups) { for (var i in newuser.groups) { targets.push('server-users:' + i); } }
|
||||
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id });
|
||||
});
|
||||
if (command.email == null) {
|
||||
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, username is ' + command.user, domain: domain.id });
|
||||
} else {
|
||||
parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: newusername, account: parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
@ -1083,7 +1150,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
parent.checkUserPassword(domain, user, command.oldpass, function (result) {
|
||||
if (result == true) {
|
||||
// Update the password
|
||||
require('./pass').hash(command.newpass, function (err, salt, hash) {
|
||||
require('./pass').hash(command.newpass, function (err, salt, hash, tag) {
|
||||
if (err) {
|
||||
// Send user notification of error
|
||||
displayNotificationMessage('Error, password not changed.', 'Account Settings', 'ServerNotify');
|
||||
|
@ -1107,7 +1174,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
// Send user notification of password change
|
||||
displayNotificationMessage('Password changed.', 'Account Settings', 'ServerNotify');
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
} else {
|
||||
// Send user notification of error
|
||||
displayNotificationMessage('Current password not correct.', 'Account Settings', 'ServerNotify');
|
||||
|
@ -1131,7 +1198,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
if ((user.groups != null) && (user.groups.length > 0) && ((chguser.groups == null) || (findOne(chguser.groups, user.groups) == false))) break;
|
||||
|
||||
// Compute the password hash & save it
|
||||
require('./pass').hash(command.pass, function (err, salt, hash) {
|
||||
require('./pass').hash(command.pass, function (err, salt, hash, tag) {
|
||||
if (!err) {
|
||||
chguser.salt = salt;
|
||||
chguser.hash = hash;
|
||||
|
@ -1156,7 +1223,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
|||
// Report that the password change failed
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "meshcentral",
|
||||
"version": "0.3.4-h",
|
||||
"version": "0.3.4-i",
|
||||
"keywords": [
|
||||
"Remote Management",
|
||||
"Intel AMT",
|
||||
|
|
13
pass.js
13
pass.js
|
@ -25,24 +25,25 @@ const iterations = 12000;
|
|||
* @param {Function} callback
|
||||
* @api public
|
||||
*/
|
||||
exports.hash = function (pwd, salt, fn) {
|
||||
if (3 == arguments.length) {
|
||||
exports.hash = function (pwd, salt, fn, tag) {
|
||||
if (4 == arguments.length) {
|
||||
try {
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { fn(err, hash.toString('base64')); });
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { fn(err, hash.toString('base64'), tag); });
|
||||
} catch (e) {
|
||||
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { fn(err, hash.toString('base64')); });
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { fn(err, hash.toString('base64'), tag); });
|
||||
}
|
||||
} else {
|
||||
tag = fn;
|
||||
fn = salt;
|
||||
crypto.randomBytes(len, function (err, salt) {
|
||||
if (err) return fn(err);
|
||||
salt = salt.toString('base64');
|
||||
try {
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64'), tag); });
|
||||
} catch (e) {
|
||||
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64'), tag); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 198 B |
File diff suppressed because one or more lines are too long
|
@ -302,6 +302,7 @@
|
|||
<div style="float:right">
|
||||
<input type=button onclick=showUserBroadcastDialog() style=margin-right:6px value="Broadcast" />
|
||||
<img onclick=p4downloadUserInfo() style="cursor:pointer" title="Download user information" src="images/link4.png" />
|
||||
<img onclick=p4batchAccountCreate() style="cursor:pointer" title="batch create many user accounts" src="images/link6.png" />
|
||||
</div>
|
||||
<div>
|
||||
<input id=UserNewAccountButton type=button style=margin-left:6px onclick=showCreateNewAccountDialog() value="New Account..." />
|
||||
|
@ -6846,6 +6847,36 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
function p4batchAccountCreate() {
|
||||
if (xxdialogMode) return;
|
||||
var x = 'Create many accounts at once by importing a JSON file with the following format:<br /><pre>[\r\n {"user":"x1","pass":"x","email":"x1@x"},\r\n {"user":"x2","pass":"x","resetNextLogin":true}\r\n]</pre><input style=width:370px type=file id=d4importFile accept=".json" onchange=p4batchAccountCreateValidate() />';
|
||||
setDialogMode(2, "User Account Import", 3, p4batchAccountCreateEx, x);
|
||||
QE('idx_dlgOkButton', false);
|
||||
}
|
||||
|
||||
function p4batchAccountCreateValidate() {
|
||||
QE('idx_dlgOkButton', Q('d4importFile').value != null);
|
||||
}
|
||||
|
||||
function p4batchAccountCreateEx() {
|
||||
var fr = new FileReader();
|
||||
fr.onload = function (r) {
|
||||
var j = null;
|
||||
try { j = JSON.parse(r.target.result); } catch (ex) { setDialogMode(2, "User Account Import", 1, null, "Invalid JSON file: " + ex + "."); return; }
|
||||
if ((j != null) && (Array.isArray(j))) {
|
||||
var ok = true;
|
||||
for (var i in j) {
|
||||
if ((typeof j[i].user != 'string') || (j[i].user.length < 1) || (j[i].user.length > 64)) { ok = false; }
|
||||
if ((typeof j[i].pass != 'string') || (j[i].pass.length < 1) || (j[i].pass.length > 256)) { ok = false; }
|
||||
if (checkPasswordRequirements(j[i].pass, passRequirements) == false) { ok = false; }
|
||||
if ((j[i].email != null) && ((typeof j[i].email != 'string') || (j[i].email.length < 1) || (j[i].email.length > 128))) { ok = false; }
|
||||
}
|
||||
if (ok == false) { setDialogMode(2, "User Account Import", 1, null, "Invalid JSON file format."); } else { meshserver.send({ action: 'adduserbatch', users: j }); }
|
||||
} else { setDialogMode(2, "User Account Import", 1, null, "Invalid JSON file format."); }
|
||||
};
|
||||
fr.readAsText(Q('d4importFile').files[0]);
|
||||
}
|
||||
|
||||
function p4downloadUserInfo() {
|
||||
if (xxdialogMode) return;
|
||||
var x = 'Download the list of users with one of the file formats below.<br /><br />';
|
||||
|
|
30
webserver.js
30
webserver.js
|
@ -388,7 +388,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
if (err) return fn(err);
|
||||
if (hash == user.hash) {
|
||||
// Update the password to the stronger format.
|
||||
require('./pass').hash(pass, function (err, salt, hash) { if (err) throw err; user.salt = salt; user.hash = hash; delete user.passtype; obj.db.SetUser(user); });
|
||||
require('./pass').hash(pass, function (err, salt, hash, tag) { if (err) throw err; user.salt = salt; user.hash = hash; delete user.passtype; obj.db.SetUser(user); }, 0);
|
||||
if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
|
||||
return fn(null, user._id);
|
||||
}
|
||||
|
@ -396,14 +396,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
});
|
||||
} else {
|
||||
// Default strong password hashing (pbkdf2 SHA384)
|
||||
require('./pass').hash(pass, user.salt, function (err, hash) {
|
||||
require('./pass').hash(pass, user.salt, function (err, hash, tag) {
|
||||
if (err) return fn(err);
|
||||
if (hash == user.hash) {
|
||||
if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { fn('locked'); return; }
|
||||
return fn(null, user._id);
|
||||
}
|
||||
fn(new Error('invalid password'), null, user.passhint);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -844,7 +844,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
req.session.userid = user._id;
|
||||
req.session.domainid = domain.id;
|
||||
// Create a user, generate a salt and hash the password
|
||||
require('./pass').hash(req.body.password1, function (err, salt, hash) {
|
||||
require('./pass').hash(req.body.password1, function (err, salt, hash, tag) {
|
||||
if (err) throw err;
|
||||
user.salt = salt;
|
||||
user.hash = hash;
|
||||
|
@ -853,7 +853,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
|
||||
// Send the verification email
|
||||
if ((obj.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (obj.common.validateEmail(user.email, 1, 256) == true)) { obj.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
|
||||
});
|
||||
}, 0);
|
||||
obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id });
|
||||
}
|
||||
res.redirect(domain.url);
|
||||
|
@ -898,7 +898,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
}
|
||||
|
||||
// Check if the password is the same as the previous one
|
||||
require('./pass').hash(req.body.rpassword1, user.salt, function (err, hash) {
|
||||
require('./pass').hash(req.body.rpassword1, user.salt, function (err, hash, tag) {
|
||||
if (user.hash == hash) {
|
||||
// This is the same password, request a password change again
|
||||
req.session.loginmode = '6';
|
||||
|
@ -906,7 +906,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
res.redirect(domain.url);
|
||||
} else {
|
||||
// Update the password, use a different salt.
|
||||
require('./pass').hash(req.body.rpassword1, function (err, salt, hash) {
|
||||
require('./pass').hash(req.body.rpassword1, function (err, salt, hash, tag) {
|
||||
if (err) throw err;
|
||||
user.salt = salt;
|
||||
user.hash = hash;
|
||||
|
@ -920,9 +920,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
req.session.userid = userid;
|
||||
req.session.domainid = domain.id;
|
||||
completeLoginRequest(req, res, domain, obj.users[userid], userid, req.session.tokenusername, req.session.tokenpassword);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
} else {
|
||||
// Failed, error out.
|
||||
delete req.session.loginmode;
|
||||
|
@ -1057,7 +1057,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
// Set a temporary password
|
||||
obj.crypto.randomBytes(16, function (err, buf) {
|
||||
var newpass = buf.toString('base64').split('=').join('').split('/').join('');
|
||||
require('./pass').hash(newpass, function (err, salt, hash) {
|
||||
require('./pass').hash(newpass, function (err, salt, hash, tag) {
|
||||
var userinfo = null;
|
||||
if (err) throw err;
|
||||
|
||||
|
@ -1076,7 +1076,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
|
||||
// Send the new password
|
||||
res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', message: '<div>Password for account <b>' + EscapeHtml(user.name) + '</b> has been reset to:</div><div style=padding:14px;font-size:18px><b>' + EscapeHtml(newpass) + '</b></div>Login and go to the \"My Account\" tab to update your password. <a href="' + domain.url + '">Go to login page</a>.' });
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -1151,14 +1151,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
});
|
||||
} else {
|
||||
// Default strong password hashing (pbkdf2 SHA384)
|
||||
require('./pass').hash(password, user.salt, function (err, hash) {
|
||||
require('./pass').hash(password, user.salt, function (err, hash, tag) {
|
||||
if (err) return func(false);
|
||||
if (hash == user.hash) {
|
||||
if ((user.siteadmin) && (user.siteadmin != 0xFFFFFFFF) && (user.siteadmin & 32) != 0) { return func(false); } // Account is locked
|
||||
return func(true); // Allow password change
|
||||
}
|
||||
func(false);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1178,7 +1178,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
obj.checkUserPassword(domain, user, req.body.apassword0, function (result) {
|
||||
if (result == true) {
|
||||
// Update the password
|
||||
require('./pass').hash(req.body.apassword1, function (err, salt, hash) {
|
||||
require('./pass').hash(req.body.apassword1, function (err, salt, hash, tag) {
|
||||
if (err) throw err;
|
||||
user.salt = salt;
|
||||
user.hash = hash;
|
||||
|
@ -1189,7 +1189,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
req.session.viewmode = 2;
|
||||
res.redirect(domain.url);
|
||||
obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, action: 'passchange', msg: 'Account password changed: ' + user.name, domain: domain.id });
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue