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">🗴</b>':' <b style=color:green title="Email is verified">🗸</b>'); }
6216
+ if (serverinfo.emailcheck == true) { emailVerified = ((user.emailVerified != true) ? ' <b style=color:red title="Email is not verified">🗴</b>' : ' <b style=color:green title="Email is verified">🗸</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, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
179
//function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); 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
/*