Session and account improvements.

Ylian Saint-Hilaire committed Feb 10, 2019 at 20:13 UTC c7ac0866483f8a6d711a835b03980d8f510419cc
8 files changed +187 -111
db.js
+1
@@ -175,6 +175,7 @@ module.exports.CreateDB = function (parent) {
175 obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }).exec(func); } else { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }, func); } };
176 obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
177 obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
178 + obj.isMaxType = function (max, type, func) { if (max == null) { func(false); } else { obj.file.count({ type: type }, function (err, count) { func((err != null) || (count > max)); }); } }
179
180 // Read a configuration file from the database
181 obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }
meshcentral.js
+1 -1
@@ -1520,7 +1520,7 @@ function mainStart(args) {
1520 if (require('os').platform() == 'win32') { for (var i in config.domains) { if (config.domains[i].auth == 'sspi') { sspi = true; } } }
1521
1522 // Build the list of required modules
1523 - var modules = ['ws', 'nedb', 'https', 'yauzl', 'xmldom', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-session', 'express-handlebars'];
1523 + var modules = ['ws', 'nedb', 'https', 'yauzl', 'xmldom', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-handlebars'];
1524 if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules
1525 if (config.letsencrypt != null) { modules.push('greenlock'); modules.push('le-store-certbot'); modules.push('le-challenge-fs'); modules.push('le-acme-core'); } // Add Greenlock Modules
1526 if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoDB
meshuser.js
+59 -26
@@ -640,19 +640,35 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
640 if ((command.email != null) && (obj.common.validateEmail(command.email, 1, 256) == false)) break; // Check if this is a valid email address
641 var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase();
642 if (newusername == '~') break; // This is a reserved user name
643 - if (!obj.parent.users[newuserid]) {
644 - var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Math.floor(Date.now() / 1000), domain: domain.id };
645 - if (command.email != null) { newuser.email = command.email; } // Email
646 - obj.parent.users[newuserid] = newuser;
647 - // Create a user, generate a salt and hash the password
648 - require('./pass').hash(command.pass, function (err, salt, hash) {
649 - if (err) throw err;
650 - newuser.salt = salt;
651 - newuser.hash = hash;
652 - obj.db.SetUser(newuser);
653 - obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: newusername, account: obj.parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id });
654 - });
655 - }
643 + if (obj.parent.users[newuserid]) break; // Account already exists
644 +
645 + // Check if we exceed the maximum number of user accounts
646 + obj.db.isMaxType(domain.maxaccounts, 'user', function (maxExceed) {
647 + if (maxExceed) {
648 + // Account count exceed, do notification
649 +
650 + // Create the notification message
651 + var notification = { "action": "msg", "type": "notify", "value": "Account limit reached.", "userid": user._id, "username": user.name };
652 +
653 + // Get the list of sessions for this user
654 + var sessions = obj.parent.wssessions[user._id];
655 + if (sessions != null) { for (i in sessions) { try { sessions[i].send(JSON.stringify(notification)); } catch (ex) { } } }
656 + // TODO: Notify all sessions on other peers.
657 + } else {
658 + // Check if this is an existing user
659 + var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Math.floor(Date.now() / 1000), domain: domain.id };
660 + if (command.email != null) { newuser.email = command.email; } // Email
661 + obj.parent.users[newuserid] = newuser;
662 + // Create a user, generate a salt and hash the password
663 + require('./pass').hash(command.pass, function (err, salt, hash) {
664 + if (err) throw err;
665 + newuser.salt = salt;
666 + newuser.hash = hash;
667 + obj.db.SetUser(newuser);
668 + obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: newusername, account: obj.parent.CloneSafeUser(newuser), action: 'accountcreate', msg: 'Account created, email is ' + command.email, domain: domain.id });
669 + });
670 + }
671 + });
672 break;
673 }
674 case 'edituser':
@@ -684,11 +700,28 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
700 if (user.siteadmin != 0xFFFFFFFF) break;
701 if (obj.common.validateString(command.user, 1, 256) == false) break;
702 if (obj.common.validateString(command.pass, 1, 256) == false) break;
703 + if (obj.common.validateString(command.hint, 0, 256) == false) break;
704 + if (typeof command.removeMultiFactor != 'boolean') break;
705 if (obj.common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements
706 +
707 var chguserid = 'user/' + domain.id + '/' + command.user.toLowerCase(), chguser = obj.parent.users[chguserid];
708 if (chguser && chguser.salt) {
709 // Compute the password hash & save it
691 - require('./pass').hash(command.pass, chguser.salt, function (err, hash) { if (!err) { chguser.hash = hash; obj.db.SetUser(chguser); } });
710 + require('./pass').hash(command.pass, chguser.salt, function (err, hash) {
711 + if (!err) {
712 + var annonceChange = false;
713 + chguser.hash = hash;
714 + chguser.passhint = command.hint;
715 + if (command.removeMultiFactor == true) {
716 + if (chguser.otpsecret) { delete chguser.otpsecret; annonceChange = true; }
717 + if (chguser.otphkeys) { delete chguser.otphkeys; annonceChange = true; }
718 + if (chguser.otpkeys) { delete chguser.otpkeys; annonceChange = true; }
719 + }
720 + obj.db.SetUser(chguser);
721 +
722 + if (annonceChange == true) { obj.parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Removed 2nd factor auth.', domain: domain.id }); }
723 + }
724 + });
725 }
726 break;
727 }
@@ -1447,8 +1480,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1480 obj.parent.db.SetUser(user);
1481 ws.send(JSON.stringify({ action: 'otpauth-setup', success: true })); // Report success
1482
1450 - // Notify change TODO: Should be done on all sessions/servers for this user.
1451 - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { }
1483 + // Notify change
1484 + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added authentication application.', domain: domain.id });
1485 } else {
1486 ws.send(JSON.stringify({ action: 'otpauth-setup', success: false })); // Report fail
1487 }
@@ -1464,10 +1497,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1497 if (user.otpsecret) {
1498 delete user.otpsecret;
1499 obj.parent.db.SetUser(user);
1500 + ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success
1501
1502 // Notify change
1469 - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { }
1470 - ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success
1503 + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', msg: 'Removed authentication application.', domain: domain.id });
1504 } else {
1505 ws.send(JSON.stringify({ action: 'otpauth-clear', success: false })); // Report fail
1506 }
@@ -1501,8 +1534,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1534 ws.send(JSON.stringify({ action: 'otpauth-getpasswords', passwords: user.otpkeys ? user.otpkeys.keys : null }));
1535 }
1536
1504 - // Notify change TODO: Should be done on all sessions/servers for this user.
1505 - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { }
1537 + // Notify change
1538 + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
1539 break;
1540 }
1541 case 'otp-hkey-get':
@@ -1532,8 +1565,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1565 obj.parent.db.SetUser(user);
1566 }
1567
1535 - // Notify change TODO: Should be done on all sessions/servers for this user.
1536 - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { }
1568 + // Notify change
1569 + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', msg: 'Removed security key.', domain: domain.id });
1570 break;
1571 }
1572 case 'otp-hkey-yubikey-add':
@@ -1568,7 +1601,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1601 ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: true, name: command.name, index: keyIndex }));
1602
1603 // Notify change TODO: Should be done on all sessions/servers for this user.
1571 - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { }
1604 + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
1605 } else {
1606 ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
1607 }
@@ -1612,10 +1645,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1645 if (user.otphkeys == null) { user.otphkeys = []; }
1646 user.otphkeys.push({ name: command.name, type: 1, publicKey: registrationStatus.publicKey, keyHandle: registrationStatus.keyHandle, certificate: registrationStatus.certificate, keyIndex: keyIndex });
1647 obj.parent.db.SetUser(user);
1615 -
1616 - // Notify change TODO: Should be done on all sessions/servers for this user.
1617 - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { }
1648 delete obj.hardwareKeyRegistrationRequest;
1649 +
1650 + // Notify change
1651 + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
1652 }, function (error) {
1653 ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex }));
1654 delete obj.hardwareKeyRegistrationRequest;
package.json
+1 -2
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.2.7-t",
3 + "version": "0.2.7-u",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
@@ -34,7 +34,6 @@
34 "cookie-session": "^2.0.0-beta.3",
35 "express": "^4.16.4",
36 "express-handlebars": "^3.0.0",
37 - "express-session": "^1.15.6",
37 "express-ws": "^4.0.0",
38 "ipcheck": "^0.1.0",
39 "meshcentral": "*",
public/images/key12.png
Binary files /dev/null and b/public/images/key12.png differ
public/images/padlock12.png
Binary files /dev/null and b/public/images/padlock12.png differ
views/default.handlebars
+29 -11
@@ -6213,8 +6213,12 @@
6213 if ((user.quota != null) && ((user.siteadmin & 8) != 0)) { msg += ", " + (user.quota / 1024) + " k"; }
6214 if (self) { msg += "</a>"; }
6215 var username = EscapeHtml(user.name), emailVerified = '';
6216 - if (serverinfo.emailcheck == true) { emailVerified = ((user.emailVerified != true)?' <b style=color:red title="Email is not verified">&#x1F5F4</b>':' <b style=color:green title="Email is verified">&#x1F5F8</b>'); }
6216 + if (serverinfo.emailcheck == true) { emailVerified = ((user.emailVerified != true) ? ' <b style=color:red title="Email is not verified">&#x1F5F4</b>' : ' <b style=color:green title="Email is verified">&#x1F5F8</b>'); }
6217 if (user.email != null) { username += ', <a onclick=doemail(event,\"' + user.email + '\")>' + user.email + '</a>' + emailVerified; }
6218 +
6219 + if ((user.otpsecret > 0) || (user.otphkeys > 0)) { username += ' <img src="images/key12.png" height=12 width=11 title="2nd factor authentication enabled" style="margin-top:2px" />'; }
6220 + if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="Account is locked" style="margin-top:2px" />'; }
6221 +
6222 x += '<tr onmouseover=userMouseHover(this,1) onmouseout=userMouseHover(this,0)><td style=cursor:pointer onclick=gotoUser(\"' + encodeURIComponent(user._id) + '\")>';
6223 x += '<div class=bar style=height:24px;width:100%;font-size:medium>';
6224 x += '<div style=float:left;height:24px;width:24px;background-color:white><div class="' + icon + gray + '" style=width:16px;margin-top:4px;margin-left:2px;height:16px></div></div>';
@@ -6270,8 +6274,8 @@
6274 Q('p4name').focus();
6275 }
6276
6273 - function showCreateNewAccountDialogValidate() {
6274 - if ((Q('p4email').value.length > 0) && (validateEmail(Q('p4email').value)) == false) { QE('idx_dlgOkButton', false); return; }
6277 + function showCreateNewAccountDialogValidate(x) {
6278 + if ((x == null) && (Q('p4email').value.length > 0) && (validateEmail(Q('p4email').value)) == false) { QE('idx_dlgOkButton', false); return; }
6279 QE('idx_dlgOkButton', (!Q('p4name') || ((Q('p4name').value.length > 0) && (Q('p4name').value.indexOf(' ') == -1))) && Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value && checkPasswordRequirements(Q('p4pass1').value, passRequirements));
6280 }
6281
@@ -6374,6 +6378,15 @@
6378 if (user.quota) x += addDeviceAttribute('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k');
6379 x += addDeviceAttribute('Creation', new Date(user.creation * 1000).toLocaleString());
6380 if (user.login) x += addDeviceAttribute('Last Login', new Date(user.login * 1000).toLocaleString());
6381 + var multiFactor = 0;
6382 + if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpkeys > 0)) {
6383 + multiFactor = 1;
6384 + var factors = [];
6385 + if (user.otpsecret > 0) { factors.push('Authentication App'); }
6386 + if (user.otphkeys > 0) { factors.push('Security Key'); }
6387 + if (user.otpkeys > 0) { factors.push('Backup Codes'); }
6388 + x += addDeviceAttribute('Security', factors.join(', '));
6389 + }
6390
6391 x += '</table></div><br />';
6392
@@ -6396,7 +6409,7 @@
6409 x = '<div style=float:right;font-size:x-small>';
6410 if (deletePossible) x += '<a style=cursor:pointer onclick=p30showDeleteUserDialog() title="Remove this user">Delete User</a>';
6411 x += '</div><div style=font-size:x-small>';
6399 - if (userinfo.siteadmin == 0xFFFFFFFF) x += '<a style=cursor:pointer onclick=p30showUserChangePassDialog() title="Change the password for this user">Change Password</a>';
6412 + if (userinfo.siteadmin == 0xFFFFFFFF) x += '<a style=cursor:pointer onclick=p30showUserChangePassDialog(' + multiFactor + ') title="Change the password for this user">Change Password</a>';
6413 x += '</div><br>'
6414 QH('p30html3', x);
6415
@@ -6440,18 +6453,23 @@
6453 }
6454
6455 // Display the user's password change dialog box
6443 - function p30showUserChangePassDialog() {
6456 + function p30showUserChangePassDialog(multiFactor) {
6457 if (xxdialogMode) return;
6458 var x = '';
6446 - x += addHtmlValue('Password', '<input id=p4pass1 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
6447 - x += addHtmlValue('Password', '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
6448 - setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x);
6449 - showCreateNewAccountDialogValidate();
6459 + x += addHtmlValue('Password', '<input id=p4pass1 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate(1) onkeyup=showCreateNewAccountDialogValidate(1)></input>');
6460 + x += addHtmlValue('Password', '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate(1) onkeyup=showCreateNewAccountDialogValidate(1)></input>');
6461 + x += addHtmlValue('Password hint', '<input id=p4hint type=text style=width:230px maxlength=256></input>');
6462 + if (multiFactor == 1) { x += '<input id=p4twoFactorRemove type=checkbox />Remove all 2nd factor authentication.'; }
6463 + setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x, multiFactor);
6464 + showCreateNewAccountDialogValidate(1);
6465 Q('p4pass1').focus();
6466 }
6467
6453 - function p30showUserChangePassDialogEx() {
6454 - if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value }); } }
6468 + function p30showUserChangePassDialogEx(b, tag) {
6469 + var removeMultiFactor = false;
6470 + if ((tag == 1) && (Q('p4twoFactorRemove').checked == true)) { removeMultiFactor = true; }
6471 + if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value, hint: Q('p4hint').value, removeMultiFactor: removeMultiFactor }); }
6472 + }
6473
6474 function p30showDeleteUserDialog() {
6475 if (xxdialogMode) return;
webserver.js
+96 -71
@@ -178,24 +178,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
178 function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
179 //function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
180
181 - // Session-persisted message middleware
182 - obj.app.use(function (req, res, next) {
183 - var err = null, msg = null, passhint = null;
184 - if (req.session != null) {
185 - err = req.session.error;
186 - msg = req.session.success;
187 - passhint = req.session.passhint;
188 - delete req.session.error;
189 - delete req.session.success;
190 - delete req.session.passhint;
191 - }
192 - res.locals.message = '';
193 - if (err != null) res.locals.message = '<p class="msg error">' + err + '</p>';
194 - if (msg != null) res.locals.message = '<p class="msg success">' + msg + '</p>';
195 - if (passhint != null) res.locals.passhint = EscapeHtml(passhint);
196 - next();
197 - });
198 -
181 // Fetch all users from the database, keep this in memory
182 obj.db.GetAllType('user', function (err, docs) {
183 var domainUserCount = {}, i = 0;
@@ -377,7 +359,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
359 if ((domain.yubikey != null) && (domain.yubikey.id != null) && (domain.yubikey.secret != null) && (user.otphkeys != null) && (user.otphkeys.length > 0) && (typeof (token) == 'string') && (token.length == 44)) {
360 var keyId = token.substring(0, 12);
361
380 - // Find a matching OPT key
362 + // Find a matching OTP key
363 var match = false;
364 for (var i = 0; i < user.otphkeys.length; i++) { if ((user.otphkeys[i].type === 2) && (user.otphkeys[i].keyid === keyId)) { match = true; } }
365
@@ -441,10 +423,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
423 checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
424 if (result == false) {
425 // 2-step auth is required, but the token is not present or not valid.
444 - if (user.otpsecret != null) { req.session.error = '<b style=color:#8C001A>Invalid token, try again.</b>'; }
426 + if ((req.body.token != null) || (req.body.hwtoken != null)) { req.session.error = '<b style=color:#8C001A>Invalid token, try again.</b>'; }
427 req.session.loginmode = '4';
428 req.session.tokenusername = xusername;
429 req.session.tokenpassword = xpassword;
430 + req.session.tokenRetry = true;
431 res.redirect(domain.url);
432 } else {
433 // Login succesful
@@ -457,12 +440,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
440 // Login succesful
441 completeLoginRequest(req, res, domain, user, userid);
442 } else {
443 + //console.log('passhint', passhint);
444 delete req.session.loginmode;
445 if (err == 'locked') { req.session.error = '<b style=color:#8C001A>Account locked.</b>'; } else { req.session.error = '<b style=color:#8C001A>Login failed, check username and password.</b>'; }
446 if ((passhint != null) && (passhint.length > 0)) {
447 req.session.passhint = passhint;
448 } else {
465 - if (req.session.passhint) { delete req.session.passhint; }
449 + delete req.session.passhint;
450 }
451 res.redirect(domain.url);
452 }
@@ -481,10 +465,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
465 delete req.session.loginmode;
466 delete req.session.tokenusername;
467 delete req.session.tokenpassword;
468 + delete req.session.success;
469 + delete req.session.error;
470 + delete req.session.passhint;
471 req.session.userid = userid;
472 req.session.domainid = domain.id;
473 req.session.currentNode = '';
487 - if (req.session.passhint) { delete req.session.passhint; }
474 if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
475 if (req.body.host) {
476 // TODO: This is a terrible search!!! FIX THIS.
@@ -515,56 +501,67 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
501 if ((domain == null) || (domain.auth == 'sspi')) return;
502
503 if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; }
518 - if (!obj.common.validateUsername(req.body.username, 1, 64) || !obj.common.validateEmail(req.body.email, 1, 256) || !obj.common.validateString(req.body.password1, 1, 256) || !obj.common.validateString(req.body.password2, 1, 256) || (req.body.password1 != req.body.password2) || req.body.username == '~' || !obj.common.checkPasswordRequirements(req.body.password1, domain.passwordrequirements)) {
519 - req.session.loginmode = 2;
520 - req.session.error = '<b style=color:#8C001A>Unable to create account.</b>';
521 - res.redirect(domain.url);
522 - } else {
523 - // Check if this email was already verified
524 - obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) {
525 - if (docs.length > 0) {
504 +
505 + // Check if we exceed the maximum number of user accounts
506 + obj.db.isMaxType(domain.maxaccounts, 'user', function (maxExceed) {
507 + if (maxExceed) {
508 + req.session.loginmode = 2;
509 + req.session.error = '<b style=color:#8C001A>Account limit reached.</b>';
510 + console.log('max', req.session);
511 + res.redirect(domain.url);
512 + } else {
513 + if (!obj.common.validateUsername(req.body.username, 1, 64) || !obj.common.validateEmail(req.body.email, 1, 256) || !obj.common.validateString(req.body.password1, 1, 256) || !obj.common.validateString(req.body.password2, 1, 256) || (req.body.password1 != req.body.password2) || req.body.username == '~' || !obj.common.checkPasswordRequirements(req.body.password1, domain.passwordrequirements)) {
514 req.session.loginmode = 2;
527 - req.session.error = '<b style=color:#8C001A>Existing account with this email address.</b>';
515 + req.session.error = '<b style=color:#8C001A>Unable to create account.</b>';
516 res.redirect(domain.url);
517 } else {
530 - // Check if there is domain.newAccountToken, check if supplied token is valid
531 - if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
532 - req.session.loginmode = 2;
533 - req.session.error = '<b style=color:#8C001A>Invalid account creation token.</b>';
534 - res.redirect(domain.url);
535 - return;
536 - }
537 - // Check if user exists
538 - if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
539 - req.session.loginmode = 2;
540 - req.session.error = '<b style=color:#8C001A>Username already exists.</b>';
541 - } else {
542 - var hint = req.body.apasswordhint;
543 - if (hint.length > 250) hint = hint.substring(0, 250);
544 - var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), domain: domain.id, passhint: hint };
545 - var usercount = 0;
546 - for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
547 - if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newaccounts === 2) { domain.newaccounts = 0; } } // If this is the first user, give the account site admin.
548 - obj.users[user._id] = user;
549 - req.session.userid = user._id;
550 - req.session.domainid = domain.id;
551 - // Create a user, generate a salt and hash the password
552 - require('./pass').hash(req.body.password1, function (err, salt, hash) {
553 - if (err) throw err;
554 - user.salt = salt;
555 - user.hash = hash;
556 - obj.db.SetUser(user);
557 -
558 - // Send the verification email
559 - if ((obj.parent.mailserver != null) && (domain.auth != 'sspi') && (obj.common.validateEmail(user.email, 1, 256) == true)) { obj.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
560 -
561 - });
562 - 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 });
563 - }
564 - res.redirect(domain.url);
518 + // Check if this email was already verified
519 + obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) {
520 + if (docs.length > 0) {
521 + req.session.loginmode = 2;
522 + req.session.error = '<b style=color:#8C001A>Existing account with this email address.</b>';
523 + res.redirect(domain.url);
524 + } else {
525 + // Check if there is domain.newAccountToken, check if supplied token is valid
526 + if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) {
527 + req.session.loginmode = 2;
528 + req.session.error = '<b style=color:#8C001A>Invalid account creation token.</b>';
529 + res.redirect(domain.url);
530 + return;
531 + }
532 + // Check if user exists
533 + if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) {
534 + req.session.loginmode = 2;
535 + req.session.error = '<b style=color:#8C001A>Username already exists.</b>';
536 + } else {
537 + var hint = req.body.apasswordhint;
538 + if (hint.length > 250) hint = hint.substring(0, 250);
539 + var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Math.floor(Date.now() / 1000), login: Math.floor(Date.now() / 1000), domain: domain.id, passhint: hint };
540 + var usercount = 0;
541 + for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } }
542 + if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newaccounts === 2) { domain.newaccounts = 0; } } // If this is the first user, give the account site admin.
543 + obj.users[user._id] = user;
544 + req.session.userid = user._id;
545 + req.session.domainid = domain.id;
546 + // Create a user, generate a salt and hash the password
547 + require('./pass').hash(req.body.password1, function (err, salt, hash) {
548 + if (err) throw err;
549 + user.salt = salt;
550 + user.hash = hash;
551 + obj.db.SetUser(user);
552 +
553 + // Send the verification email
554 + if ((obj.parent.mailserver != null) && (domain.auth != 'sspi') && (obj.common.validateEmail(user.email, 1, 256) == true)) { obj.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
555 +
556 + });
557 + 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 });
558 + }
559 + res.redirect(domain.url);
560 + }
561 + });
562 }
566 - });
567 - }
563 + }
564 + });
565 }
566
567 // Called to process an account reset request
@@ -853,6 +850,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
850 if (req.session && req.session.userid && obj.users[req.session.userid]) {
851 var user = obj.users[req.session.userid];
852 if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain
853 +
854 + // Check if this is a locked account
855 + if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) {
856 + // Locked account
857 + delete req.session.userid;
858 + delete req.session.domainid;
859 + delete req.session.currentNode;
860 + delete req.session.passhint;
861 + req.session.error = '<b style=color:#8C001A>Account locked.</b>';
862 + res.redirect(domain.url);
863 + return;
864 + }
865 +
866 var viewmode = 1;
867 if (req.session.viewmode) {
868 viewmode = req.session.viewmode;
@@ -928,17 +938,32 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
938 var loginmode = req.session.loginmode;
939 delete req.session.loginmode; // Clear this state, if the user hits refresh, we want to go back to the login page.
940
941 + // Format an error message if needed
942 + var err = null, msg = null, passhint = null;
943 + if (req.session != null) {
944 + err = req.session.error;
945 + msg = req.session.success;
946 + passhint = req.session.passhint;
947 + delete req.session.error;
948 + delete req.session.success;
949 + delete req.session.passhint;
950 + }
951 + var message = '';
952 + if (err != null) message = '<p class="msg error">' + err + '</p>';
953 + if (msg != null) message = '<p class="msg success">' + msg + '</p>';
954 + if (passhint != null) passhint = EscapeHtml(passhint);
955 +
956 if (obj.args.minify && !req.query.nominify) {
957 // Try to server the minified version if we can.
958 try {
934 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile-min' : 'login-min'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: hardwareKeyChallenge });
959 + res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile-min' : 'login-min'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: hardwareKeyChallenge, message: message, passhint: passhint });
960 } catch (ex) {
961 // In case of an exception, serve the non-minified version.
937 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile' : 'login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: hardwareKeyChallenge });
962 + res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile' : 'login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: hardwareKeyChallenge, message: message, passhint: passhint });
963 }
964 } else {
965 // Serve non-minified version of web pages.
941 - res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile' : 'login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: hardwareKeyChallenge });
966 + res.render(obj.path.join(obj.parent.webViewsPath, isMobileBrowser(req) ? 'login-mobile' : 'login'), { loginmode: loginmode, rootCertLink: getRootCertLink(), title: domain.title, title2: domain.title2, newAccount: domain.newaccounts, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: obj.parent.mailserver != null, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: hardwareKeyChallenge, message: message, passhint: passhint });
967 }
968
969 /*