Session and account improvements.

This commit is contained in:
Ylian Saint-Hilaire 2019-02-10 20:13:03 -08:00
parent f1e9d83cc9
commit a932a66044
8 changed files with 185 additions and 109 deletions

1
db.js
View File

@ -175,6 +175,7 @@ module.exports.CreateDB = function (parent) {
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); } }; 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); } };
obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); }; obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); };
obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); }; obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); };
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)); }); } }
// Read a configuration file from the database // Read a configuration file from the database
obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); } obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); }

View File

@ -1520,7 +1520,7 @@ function mainStart(args) {
if (require('os').platform() == 'win32') { for (var i in config.domains) { if (config.domains[i].auth == 'sspi') { sspi = true; } } } if (require('os').platform() == 'win32') { for (var i in config.domains) { if (config.domains[i].auth == 'sspi') { sspi = true; } } }
// Build the list of required modules // Build the list of required modules
var modules = ['ws', 'nedb', 'https', 'yauzl', 'xmldom', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-session', 'express-handlebars']; var modules = ['ws', 'nedb', 'https', 'yauzl', 'xmldom', 'express', 'archiver', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-handlebars'];
if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules if (require('os').platform() == 'win32') { modules.push('node-windows'); if (sspi == true) { modules.push('node-sspi'); } } // Add Windows modules
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 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
if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoDB if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoDB

View File

@ -640,7 +640,22 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if ((command.email != null) && (obj.common.validateEmail(command.email, 1, 256) == false)) break; // Check if this is a valid email address if ((command.email != null) && (obj.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(); var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase();
if (newusername == '~') break; // This is a reserved user name if (newusername == '~') break; // This is a reserved user name
if (!obj.parent.users[newuserid]) { if (obj.parent.users[newuserid]) break; // Account already exists
// Check if we exceed the maximum number of user accounts
obj.db.isMaxType(domain.maxaccounts, 'user', function (maxExceed) {
if (maxExceed) {
// Account count exceed, do notification
// Create the notification message
var notification = { "action": "msg", "type": "notify", "value": "Account limit reached.", "userid": user._id, "username": user.name };
// Get the list of sessions for this user
var sessions = obj.parent.wssessions[user._id];
if (sessions != null) { for (i in sessions) { try { sessions[i].send(JSON.stringify(notification)); } catch (ex) { } } }
// TODO: Notify all sessions on other peers.
} else {
// Check if this is an existing user
var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Math.floor(Date.now() / 1000), domain: domain.id }; var newuser = { type: 'user', _id: newuserid, name: newusername, creation: Math.floor(Date.now() / 1000), domain: domain.id };
if (command.email != null) { newuser.email = command.email; } // Email if (command.email != null) { newuser.email = command.email; } // Email
obj.parent.users[newuserid] = newuser; obj.parent.users[newuserid] = newuser;
@ -653,6 +668,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
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 }); 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 });
}); });
} }
});
break; break;
} }
case 'edituser': case 'edituser':
@ -684,11 +700,28 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (user.siteadmin != 0xFFFFFFFF) break; if (user.siteadmin != 0xFFFFFFFF) break;
if (obj.common.validateString(command.user, 1, 256) == false) break; if (obj.common.validateString(command.user, 1, 256) == false) break;
if (obj.common.validateString(command.pass, 1, 256) == false) break; if (obj.common.validateString(command.pass, 1, 256) == false) break;
if (obj.common.validateString(command.hint, 0, 256) == false) break;
if (typeof command.removeMultiFactor != 'boolean') break;
if (obj.common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements if (obj.common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) break; // Password does not meet requirements
var chguserid = 'user/' + domain.id + '/' + command.user.toLowerCase(), chguser = obj.parent.users[chguserid]; var chguserid = 'user/' + domain.id + '/' + command.user.toLowerCase(), chguser = obj.parent.users[chguserid];
if (chguser && chguser.salt) { if (chguser && chguser.salt) {
// Compute the password hash & save it // Compute the password hash & save it
require('./pass').hash(command.pass, chguser.salt, function (err, hash) { if (!err) { chguser.hash = hash; obj.db.SetUser(chguser); } }); require('./pass').hash(command.pass, chguser.salt, function (err, hash) {
if (!err) {
var annonceChange = false;
chguser.hash = hash;
chguser.passhint = command.hint;
if (command.removeMultiFactor == true) {
if (chguser.otpsecret) { delete chguser.otpsecret; annonceChange = true; }
if (chguser.otphkeys) { delete chguser.otphkeys; annonceChange = true; }
if (chguser.otpkeys) { delete chguser.otpkeys; annonceChange = true; }
}
obj.db.SetUser(chguser);
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 }); }
}
});
} }
break; break;
} }
@ -1447,8 +1480,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
obj.parent.db.SetUser(user); obj.parent.db.SetUser(user);
ws.send(JSON.stringify({ action: 'otpauth-setup', success: true })); // Report success ws.send(JSON.stringify({ action: 'otpauth-setup', success: true })); // Report success
// Notify change TODO: Should be done on all sessions/servers for this user. // Notify change
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } 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 });
} else { } else {
ws.send(JSON.stringify({ action: 'otpauth-setup', success: false })); // Report fail ws.send(JSON.stringify({ action: 'otpauth-setup', success: false })); // Report fail
} }
@ -1464,10 +1497,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (user.otpsecret) { if (user.otpsecret) {
delete user.otpsecret; delete user.otpsecret;
obj.parent.db.SetUser(user); obj.parent.db.SetUser(user);
ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success
// Notify change // Notify change
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } 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 });
ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success
} else { } else {
ws.send(JSON.stringify({ action: 'otpauth-clear', success: false })); // Report fail ws.send(JSON.stringify({ action: 'otpauth-clear', success: false })); // Report fail
} }
@ -1501,8 +1534,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
ws.send(JSON.stringify({ action: 'otpauth-getpasswords', passwords: user.otpkeys ? user.otpkeys.keys : null })); ws.send(JSON.stringify({ action: 'otpauth-getpasswords', passwords: user.otpkeys ? user.otpkeys.keys : null }));
} }
// Notify change TODO: Should be done on all sessions/servers for this user. // Notify change
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } 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 });
break; break;
} }
case 'otp-hkey-get': case 'otp-hkey-get':
@ -1532,8 +1565,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
obj.parent.db.SetUser(user); obj.parent.db.SetUser(user);
} }
// Notify change TODO: Should be done on all sessions/servers for this user. // Notify change
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } 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 });
break; break;
} }
case 'otp-hkey-yubikey-add': case 'otp-hkey-yubikey-add':
@ -1568,7 +1601,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: true, name: command.name, index: keyIndex })); ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: true, name: command.name, index: keyIndex }));
// Notify change TODO: Should be done on all sessions/servers for this user. // Notify change TODO: Should be done on all sessions/servers for this user.
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } 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 });
} else { } else {
ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name })); ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
} }
@ -1612,10 +1645,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (user.otphkeys == null) { user.otphkeys = []; } if (user.otphkeys == null) { user.otphkeys = []; }
user.otphkeys.push({ name: command.name, type: 1, publicKey: registrationStatus.publicKey, keyHandle: registrationStatus.keyHandle, certificate: registrationStatus.certificate, keyIndex: keyIndex }); user.otphkeys.push({ name: command.name, type: 1, publicKey: registrationStatus.publicKey, keyHandle: registrationStatus.keyHandle, certificate: registrationStatus.certificate, keyIndex: keyIndex });
obj.parent.db.SetUser(user); obj.parent.db.SetUser(user);
// Notify change TODO: Should be done on all sessions/servers for this user.
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { }
delete obj.hardwareKeyRegistrationRequest; delete obj.hardwareKeyRegistrationRequest;
// Notify change
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 });
}, function (error) { }, function (error) {
ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex })); ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex }));
delete obj.hardwareKeyRegistrationRequest; delete obj.hardwareKeyRegistrationRequest;

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.2.7-t", "version": "0.2.7-u",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",
@ -34,7 +34,6 @@
"cookie-session": "^2.0.0-beta.3", "cookie-session": "^2.0.0-beta.3",
"express": "^4.16.4", "express": "^4.16.4",
"express-handlebars": "^3.0.0", "express-handlebars": "^3.0.0",
"express-session": "^1.15.6",
"express-ws": "^4.0.0", "express-ws": "^4.0.0",
"ipcheck": "^0.1.0", "ipcheck": "^0.1.0",
"meshcentral": "*", "meshcentral": "*",

BIN
public/images/key12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

BIN
public/images/padlock12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

View File

@ -6213,8 +6213,12 @@
if ((user.quota != null) && ((user.siteadmin & 8) != 0)) { msg += ", " + (user.quota / 1024) + " k"; } if ((user.quota != null) && ((user.siteadmin & 8) != 0)) { msg += ", " + (user.quota / 1024) + " k"; }
if (self) { msg += "</a>"; } if (self) { msg += "</a>"; }
var username = EscapeHtml(user.name), emailVerified = ''; var username = EscapeHtml(user.name), emailVerified = '';
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>'); } 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>'); }
if (user.email != null) { username += ', <a onclick=doemail(event,\"' + user.email + '\")>' + user.email + '</a>' + emailVerified; } if (user.email != null) { username += ', <a onclick=doemail(event,\"' + user.email + '\")>' + user.email + '</a>' + emailVerified; }
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" />'; }
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" />'; }
x += '<tr onmouseover=userMouseHover(this,1) onmouseout=userMouseHover(this,0)><td style=cursor:pointer onclick=gotoUser(\"' + encodeURIComponent(user._id) + '\")>'; x += '<tr onmouseover=userMouseHover(this,1) onmouseout=userMouseHover(this,0)><td style=cursor:pointer onclick=gotoUser(\"' + encodeURIComponent(user._id) + '\")>';
x += '<div class=bar style=height:24px;width:100%;font-size:medium>'; x += '<div class=bar style=height:24px;width:100%;font-size:medium>';
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>'; 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 @@
Q('p4name').focus(); Q('p4name').focus();
} }
function showCreateNewAccountDialogValidate() { function showCreateNewAccountDialogValidate(x) {
if ((Q('p4email').value.length > 0) && (validateEmail(Q('p4email').value)) == false) { QE('idx_dlgOkButton', false); return; } if ((x == null) && (Q('p4email').value.length > 0) && (validateEmail(Q('p4email').value)) == false) { QE('idx_dlgOkButton', false); return; }
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)); 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));
} }
@ -6374,6 +6378,15 @@
if (user.quota) x += addDeviceAttribute('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k'); if (user.quota) x += addDeviceAttribute('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k');
x += addDeviceAttribute('Creation', new Date(user.creation * 1000).toLocaleString()); x += addDeviceAttribute('Creation', new Date(user.creation * 1000).toLocaleString());
if (user.login) x += addDeviceAttribute('Last Login', new Date(user.login * 1000).toLocaleString()); if (user.login) x += addDeviceAttribute('Last Login', new Date(user.login * 1000).toLocaleString());
var multiFactor = 0;
if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpkeys > 0)) {
multiFactor = 1;
var factors = [];
if (user.otpsecret > 0) { factors.push('Authentication App'); }
if (user.otphkeys > 0) { factors.push('Security Key'); }
if (user.otpkeys > 0) { factors.push('Backup Codes'); }
x += addDeviceAttribute('Security', factors.join(', '));
}
x += '</table></div><br />'; x += '</table></div><br />';
@ -6396,7 +6409,7 @@
x = '<div style=float:right;font-size:x-small>'; x = '<div style=float:right;font-size:x-small>';
if (deletePossible) x += '<a style=cursor:pointer onclick=p30showDeleteUserDialog() title="Remove this user">Delete User</a>'; if (deletePossible) x += '<a style=cursor:pointer onclick=p30showDeleteUserDialog() title="Remove this user">Delete User</a>';
x += '</div><div style=font-size:x-small>'; x += '</div><div style=font-size:x-small>';
if (userinfo.siteadmin == 0xFFFFFFFF) x += '<a style=cursor:pointer onclick=p30showUserChangePassDialog() title="Change the password for this user">Change Password</a>'; if (userinfo.siteadmin == 0xFFFFFFFF) x += '<a style=cursor:pointer onclick=p30showUserChangePassDialog(' + multiFactor + ') title="Change the password for this user">Change Password</a>';
x += '</div><br>' x += '</div><br>'
QH('p30html3', x); QH('p30html3', x);
@ -6440,18 +6453,23 @@
} }
// Display the user's password change dialog box // Display the user's password change dialog box
function p30showUserChangePassDialog() { function p30showUserChangePassDialog(multiFactor) {
if (xxdialogMode) return; if (xxdialogMode) return;
var x = ''; var x = '';
x += addHtmlValue('Password', '<input id=p4pass1 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />'); x += addHtmlValue('Password', '<input id=p4pass1 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate(1) onkeyup=showCreateNewAccountDialogValidate(1)></input>');
x += addHtmlValue('Password', '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />'); x += addHtmlValue('Password', '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=showCreateNewAccountDialogValidate(1) onkeyup=showCreateNewAccountDialogValidate(1)></input>');
setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x); x += addHtmlValue('Password hint', '<input id=p4hint type=text style=width:230px maxlength=256></input>');
showCreateNewAccountDialogValidate(); if (multiFactor == 1) { x += '<input id=p4twoFactorRemove type=checkbox />Remove all 2nd factor authentication.'; }
setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x, multiFactor);
showCreateNewAccountDialogValidate(1);
Q('p4pass1').focus(); Q('p4pass1').focus();
} }
function p30showUserChangePassDialogEx() { function p30showUserChangePassDialogEx(b, tag) {
if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value }); } } var removeMultiFactor = false;
if ((tag == 1) && (Q('p4twoFactorRemove').checked == true)) { removeMultiFactor = true; }
if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value, hint: Q('p4hint').value, removeMultiFactor: removeMultiFactor }); }
}
function p30showDeleteUserDialog() { function p30showDeleteUserDialog() {
if (xxdialogMode) return; if (xxdialogMode) return;

View File

@ -178,24 +178,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
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; } 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; }
//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; } //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; }
// Session-persisted message middleware
obj.app.use(function (req, res, next) {
var err = null, msg = null, passhint = null;
if (req.session != null) {
err = req.session.error;
msg = req.session.success;
passhint = req.session.passhint;
delete req.session.error;
delete req.session.success;
delete req.session.passhint;
}
res.locals.message = '';
if (err != null) res.locals.message = '<p class="msg error">' + err + '</p>';
if (msg != null) res.locals.message = '<p class="msg success">' + msg + '</p>';
if (passhint != null) res.locals.passhint = EscapeHtml(passhint);
next();
});
// Fetch all users from the database, keep this in memory // Fetch all users from the database, keep this in memory
obj.db.GetAllType('user', function (err, docs) { obj.db.GetAllType('user', function (err, docs) {
var domainUserCount = {}, i = 0; var domainUserCount = {}, i = 0;
@ -377,7 +359,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
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)) { 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)) {
var keyId = token.substring(0, 12); var keyId = token.substring(0, 12);
// Find a matching OPT key // Find a matching OTP key
var match = false; var match = false;
for (var i = 0; i < user.otphkeys.length; i++) { if ((user.otphkeys[i].type === 2) && (user.otphkeys[i].keyid === keyId)) { match = true; } } for (var i = 0; i < user.otphkeys.length; i++) { if ((user.otphkeys[i].type === 2) && (user.otphkeys[i].keyid === keyId)) { match = true; } }
@ -441,10 +423,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) { checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) {
if (result == false) { if (result == false) {
// 2-step auth is required, but the token is not present or not valid. // 2-step auth is required, but the token is not present or not valid.
if (user.otpsecret != null) { req.session.error = '<b style=color:#8C001A>Invalid token, try again.</b>'; } if ((req.body.token != null) || (req.body.hwtoken != null)) { req.session.error = '<b style=color:#8C001A>Invalid token, try again.</b>'; }
req.session.loginmode = '4'; req.session.loginmode = '4';
req.session.tokenusername = xusername; req.session.tokenusername = xusername;
req.session.tokenpassword = xpassword; req.session.tokenpassword = xpassword;
req.session.tokenRetry = true;
res.redirect(domain.url); res.redirect(domain.url);
} else { } else {
// Login succesful // Login succesful
@ -457,12 +440,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Login succesful // Login succesful
completeLoginRequest(req, res, domain, user, userid); completeLoginRequest(req, res, domain, user, userid);
} else { } else {
//console.log('passhint', passhint);
delete req.session.loginmode; delete req.session.loginmode;
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>'; } 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>'; }
if ((passhint != null) && (passhint.length > 0)) { if ((passhint != null) && (passhint.length > 0)) {
req.session.passhint = passhint; req.session.passhint = passhint;
} else { } else {
if (req.session.passhint) { delete req.session.passhint; } delete req.session.passhint;
} }
res.redirect(domain.url); res.redirect(domain.url);
} }
@ -481,10 +465,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete req.session.loginmode; delete req.session.loginmode;
delete req.session.tokenusername; delete req.session.tokenusername;
delete req.session.tokenpassword; delete req.session.tokenpassword;
delete req.session.success;
delete req.session.error;
delete req.session.passhint;
req.session.userid = userid; req.session.userid = userid;
req.session.domainid = domain.id; req.session.domainid = domain.id;
req.session.currentNode = ''; req.session.currentNode = '';
if (req.session.passhint) { delete req.session.passhint; }
if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; } if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; }
if (req.body.host) { if (req.body.host) {
// TODO: This is a terrible search!!! FIX THIS. // TODO: This is a terrible search!!! FIX THIS.
@ -515,6 +501,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((domain == null) || (domain.auth == 'sspi')) return; if ((domain == null) || (domain.auth == 'sspi')) return;
if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; } if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; }
// Check if we exceed the maximum number of user accounts
obj.db.isMaxType(domain.maxaccounts, 'user', function (maxExceed) {
if (maxExceed) {
req.session.loginmode = 2;
req.session.error = '<b style=color:#8C001A>Account limit reached.</b>';
console.log('max', req.session);
res.redirect(domain.url);
} else {
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)) { 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)) {
req.session.loginmode = 2; req.session.loginmode = 2;
req.session.error = '<b style=color:#8C001A>Unable to create account.</b>'; req.session.error = '<b style=color:#8C001A>Unable to create account.</b>';
@ -566,6 +561,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
}); });
} }
} }
});
}
// Called to process an account reset request // Called to process an account reset request
function handleResetAccountRequest(req, res) { function handleResetAccountRequest(req, res) {
@ -853,6 +850,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (req.session && req.session.userid && obj.users[req.session.userid]) { if (req.session && req.session.userid && obj.users[req.session.userid]) {
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain if (req.session.domainid != domain.id) { req.session = null; res.redirect(domain.url); return; } // Check is the session is for the correct domain
// Check if this is a locked account
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) {
// Locked account
delete req.session.userid;
delete req.session.domainid;
delete req.session.currentNode;
delete req.session.passhint;
req.session.error = '<b style=color:#8C001A>Account locked.</b>';
res.redirect(domain.url);
return;
}
var viewmode = 1; var viewmode = 1;
if (req.session.viewmode) { if (req.session.viewmode) {
viewmode = req.session.viewmode; viewmode = req.session.viewmode;
@ -928,17 +938,32 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var loginmode = req.session.loginmode; var loginmode = req.session.loginmode;
delete req.session.loginmode; // Clear this state, if the user hits refresh, we want to go back to the login page. delete req.session.loginmode; // Clear this state, if the user hits refresh, we want to go back to the login page.
// Format an error message if needed
var err = null, msg = null, passhint = null;
if (req.session != null) {
err = req.session.error;
msg = req.session.success;
passhint = req.session.passhint;
delete req.session.error;
delete req.session.success;
delete req.session.passhint;
}
var message = '';
if (err != null) message = '<p class="msg error">' + err + '</p>';
if (msg != null) message = '<p class="msg success">' + msg + '</p>';
if (passhint != null) passhint = EscapeHtml(passhint);
if (obj.args.minify && !req.query.nominify) { if (obj.args.minify && !req.query.nominify) {
// Try to server the minified version if we can. // Try to server the minified version if we can.
try { try {
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 }); 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 });
} catch (ex) { } catch (ex) {
// In case of an exception, serve the non-minified version. // In case of an exception, serve the non-minified version.
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 }); 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 });
} }
} else { } else {
// Serve non-minified version of web pages. // Serve non-minified version of web pages.
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 }); 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 });
} }
/* /*