From a932a66044da97cb8ac070bed2f8523549e27883 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Sun, 10 Feb 2019 20:13:03 -0800 Subject: [PATCH] Session and account improvements. --- db.js | 1 + meshcentral.js | 2 +- meshuser.js | 85 +++++++++++++------ package.json | 3 +- public/images/key12.png | Bin 0 -> 371 bytes public/images/padlock12.png | Bin 0 -> 381 bytes views/default.handlebars | 40 ++++++--- webserver.js | 163 +++++++++++++++++++++--------------- 8 files changed, 185 insertions(+), 109 deletions(-) create mode 100644 public/images/key12.png create mode 100644 public/images/padlock12.png diff --git a/db.js b/db.js index ab715d57..6f476a0e 100644 --- a/db.js +++ b/db.js @@ -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.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.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 obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); } diff --git a/meshcentral.js b/meshcentral.js index 0da4828b..d063cef6 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -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; } } } // 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 (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 diff --git a/meshuser.js b/meshuser.js index 10d8fbcc..51f40f98 100644 --- a/meshuser.js +++ b/meshuser.js @@ -640,19 +640,35 @@ 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 var newusername = command.username, newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(); if (newusername == '~') break; // This is a reserved user name - if (!obj.parent.users[newuserid]) { - 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 - obj.parent.users[newuserid] = newuser; - // Create a user, generate a salt and hash the password - require('./pass').hash(command.pass, function (err, salt, hash) { - if (err) throw err; - newuser.salt = salt; - newuser.hash = hash; - obj.db.SetUser(newuser); - 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 }); - }); - } + 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 }; + if (command.email != null) { newuser.email = command.email; } // Email + obj.parent.users[newuserid] = newuser; + // Create a user, generate a salt and hash the password + require('./pass').hash(command.pass, function (err, salt, hash) { + if (err) throw err; + newuser.salt = salt; + newuser.hash = hash; + obj.db.SetUser(newuser); + 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; } case 'edituser': @@ -684,11 +700,28 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (user.siteadmin != 0xFFFFFFFF) 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.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 + var chguserid = 'user/' + domain.id + '/' + command.user.toLowerCase(), chguser = obj.parent.users[chguserid]; if (chguser && chguser.salt) { // 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; } @@ -1447,8 +1480,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use obj.parent.db.SetUser(user); ws.send(JSON.stringify({ action: 'otpauth-setup', success: true })); // Report success - // 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) { } + // 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 authentication application.', domain: domain.id }); } else { 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) { delete user.otpsecret; obj.parent.db.SetUser(user); + ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success // Notify change - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } - ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success + 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 }); } else { 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 })); } - // 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) { } + // 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 }); break; } case 'otp-hkey-get': @@ -1532,8 +1565,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use 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) { } + // Notify change + 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; } 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 })); // 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 { 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 = []; } user.otphkeys.push({ name: command.name, type: 1, publicKey: registrationStatus.publicKey, keyHandle: registrationStatus.keyHandle, certificate: registrationStatus.certificate, keyIndex: keyIndex }); 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; + + // 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) { ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex })); delete obj.hardwareKeyRegistrationRequest; diff --git a/package.json b/package.json index 312c6790..48b33ab3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.7-t", + "version": "0.2.7-u", "keywords": [ "Remote Management", "Intel AMT", @@ -34,7 +34,6 @@ "cookie-session": "^2.0.0-beta.3", "express": "^4.16.4", "express-handlebars": "^3.0.0", - "express-session": "^1.15.6", "express-ws": "^4.0.0", "ipcheck": "^0.1.0", "meshcentral": "*", diff --git a/public/images/key12.png b/public/images/key12.png new file mode 100644 index 0000000000000000000000000000000000000000..cebca941952eae25436ccb36d48cb35db6fd7afb GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^+(693!3HF^taP6Tq!^2X+?^QKos)S9a~60+7BevL9R^{>#|KV)SgRwuMJ^gS4ca` zoPMYHwRLjzOxHxahMf9`)1&sizaF&Q#U^;NsI||os(I@6-`!_@V{Ch-acc6fd9#83 OW$<+Mb6Mw<&;$Tw1dXBq literal 0 HcmV?d00001 diff --git a/public/images/padlock12.png b/public/images/padlock12.png new file mode 100644 index 0000000000000000000000000000000000000000..670ccbb2dc7484ce08ae020dfecca2a5559e39bf GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^96-#&!3HGb=lz)rq!^2X+?^QKos)S9a~60+7BevL9R^{>L16??%UrX9#9})ek}4-&ZBd=?1OKA|f#Y3`YApW}?#oN_c))P=5(6RdF!++fkC?g1pH0T;^~ut^nZ}HaYx4s>d{b`>(mEklbN{)0 Y🗴':' 🗸'); } + if (serverinfo.emailcheck == true) { emailVerified = ((user.emailVerified != true) ? ' 🗴' : ' 🗸'); } if (user.email != null) { username += ', ' + user.email + '' + emailVerified; } + + if ((user.otpsecret > 0) || (user.otphkeys > 0)) { username += ' '; } + if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' '; } + x += ''; x += '
'; x += '
'; @@ -6270,8 +6274,8 @@ Q('p4name').focus(); } - function showCreateNewAccountDialogValidate() { - if ((Q('p4email').value.length > 0) && (validateEmail(Q('p4email').value)) == false) { QE('idx_dlgOkButton', false); return; } + function showCreateNewAccountDialogValidate(x) { + 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)); } @@ -6374,6 +6378,15 @@ if (user.quota) x += addDeviceAttribute('Server Quota', EscapeHtml(parseInt(user.quota) / 1024) + ' k'); x += addDeviceAttribute('Creation', new Date(user.creation * 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 += '

'; @@ -6396,7 +6409,7 @@ x = '
'; if (deletePossible) x += 'Delete User'; x += '
'; - if (userinfo.siteadmin == 0xFFFFFFFF) x += 'Change Password'; + if (userinfo.siteadmin == 0xFFFFFFFF) x += 'Change Password'; x += '

' QH('p30html3', x); @@ -6440,18 +6453,23 @@ } // Display the user's password change dialog box - function p30showUserChangePassDialog() { + function p30showUserChangePassDialog(multiFactor) { if (xxdialogMode) return; var x = ''; - x += addHtmlValue('Password', ''); - x += addHtmlValue('Password', ''); - setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x); - showCreateNewAccountDialogValidate(); + x += addHtmlValue('Password', ''); + x += addHtmlValue('Password', ''); + x += addHtmlValue('Password hint', ''); + if (multiFactor == 1) { x += 'Remove all 2nd factor authentication.'; } + setDialogMode(2, "Change Password for " + EscapeHtml(currentUser.name), 3, p30showUserChangePassDialogEx, x, multiFactor); + showCreateNewAccountDialogValidate(1); Q('p4pass1').focus(); } - function p30showUserChangePassDialogEx() { - if (Q('p4pass1').value == Q('p4pass2').value) { meshserver.send({ action: 'changeuserpass', user: currentUser.name, pass: Q('p4pass1').value }); } } + function p30showUserChangePassDialogEx(b, tag) { + 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() { if (xxdialogMode) return; diff --git a/webserver.js b/webserver.js index 2eddf3f0..de3ac682 100644 --- a/webserver.js +++ b/webserver.js @@ -178,24 +178,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(//g, '>').replace(/').replace(/\n/g, '').replace(/\t/g, '  '); 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 = '

' + err + '

'; - if (msg != null) res.locals.message = '

' + msg + '

'; - if (passhint != null) res.locals.passhint = EscapeHtml(passhint); - next(); - }); - // Fetch all users from the database, keep this in memory obj.db.GetAllType('user', function (err, docs) { 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)) { var keyId = token.substring(0, 12); - // Find a matching OPT key + // Find a matching OTP key 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; } } @@ -441,10 +423,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { checkUserOneTimePassword(req, domain, user, req.body.token, req.body.hwtoken, function (result) { if (result == false) { // 2-step auth is required, but the token is not present or not valid. - if (user.otpsecret != null) { req.session.error = 'Invalid token, try again.'; } + if ((req.body.token != null) || (req.body.hwtoken != null)) { req.session.error = 'Invalid token, try again.'; } req.session.loginmode = '4'; req.session.tokenusername = xusername; req.session.tokenpassword = xpassword; + req.session.tokenRetry = true; res.redirect(domain.url); } else { // Login succesful @@ -457,12 +440,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Login succesful completeLoginRequest(req, res, domain, user, userid); } else { + //console.log('passhint', passhint); delete req.session.loginmode; if (err == 'locked') { req.session.error = 'Account locked.'; } else { req.session.error = 'Login failed, check username and password.'; } if ((passhint != null) && (passhint.length > 0)) { req.session.passhint = passhint; } else { - if (req.session.passhint) { delete req.session.passhint; } + delete req.session.passhint; } res.redirect(domain.url); } @@ -481,10 +465,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { delete req.session.loginmode; delete req.session.tokenusername; delete req.session.tokenpassword; + delete req.session.success; + delete req.session.error; + delete req.session.passhint; req.session.userid = userid; req.session.domainid = domain.id; req.session.currentNode = ''; - if (req.session.passhint) { delete req.session.passhint; } if (req.body.viewmode) { req.session.viewmode = req.body.viewmode; } if (req.body.host) { // TODO: This is a terrible search!!! FIX THIS. @@ -515,56 +501,67 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if ((domain == null) || (domain.auth == 'sspi')) return; if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; } - 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.error = 'Unable to create account.'; - res.redirect(domain.url); - } else { - // Check if this email was already verified - obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) { - if (docs.length > 0) { + + // 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 = 'Account limit reached.'; + 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)) { req.session.loginmode = 2; - req.session.error = 'Existing account with this email address.'; + req.session.error = 'Unable to create account.'; res.redirect(domain.url); } else { - // Check if there is domain.newAccountToken, check if supplied token is valid - if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) { - req.session.loginmode = 2; - req.session.error = 'Invalid account creation token.'; - res.redirect(domain.url); - return; - } - // Check if user exists - if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) { - req.session.loginmode = 2; - req.session.error = 'Username already exists.'; - } else { - var hint = req.body.apasswordhint; - if (hint.length > 250) hint = hint.substring(0, 250); - 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 }; - var usercount = 0; - for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } } - if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newaccounts === 2) { domain.newaccounts = 0; } } // If this is the first user, give the account site admin. - obj.users[user._id] = user; - req.session.userid = user._id; - req.session.domainid = domain.id; - // Create a user, generate a salt and hash the password - require('./pass').hash(req.body.password1, function (err, salt, hash) { - if (err) throw err; - user.salt = salt; - user.hash = hash; - obj.db.SetUser(user); + // Check if this email was already verified + obj.db.GetUserWithVerifiedEmail(domain.id, req.body.email, function (err, docs) { + if (docs.length > 0) { + req.session.loginmode = 2; + req.session.error = 'Existing account with this email address.'; + res.redirect(domain.url); + } else { + // Check if there is domain.newAccountToken, check if supplied token is valid + if ((domain.newaccountspass != null) && (domain.newaccountspass != '') && (req.body.anewaccountpass != domain.newaccountspass)) { + req.session.loginmode = 2; + req.session.error = 'Invalid account creation token.'; + res.redirect(domain.url); + return; + } + // Check if user exists + if (obj.users['user/' + domain.id + '/' + req.body.username.toLowerCase()]) { + req.session.loginmode = 2; + req.session.error = 'Username already exists.'; + } else { + var hint = req.body.apasswordhint; + if (hint.length > 250) hint = hint.substring(0, 250); + 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 }; + var usercount = 0; + for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } } + if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newaccounts === 2) { domain.newaccounts = 0; } } // If this is the first user, give the account site admin. + obj.users[user._id] = user; + req.session.userid = user._id; + req.session.domainid = domain.id; + // Create a user, generate a salt and hash the password + require('./pass').hash(req.body.password1, function (err, salt, hash) { + if (err) throw err; + user.salt = salt; + user.hash = hash; + obj.db.SetUser(user); - // Send the verification email - 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); } + // Send the verification email + 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); } - }); - obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id }); - } - res.redirect(domain.url); + }); + obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id }); + } + res.redirect(domain.url); + } + }); } - }); - } + } + }); } // Called to process an account reset request @@ -853,6 +850,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (req.session && req.session.userid && 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 + + // 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 = 'Account locked.'; + res.redirect(domain.url); + return; + } + var viewmode = 1; if (req.session.viewmode) { viewmode = req.session.viewmode; @@ -928,17 +938,32 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { 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. + // 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 = '

' + err + '

'; + if (msg != null) message = '

' + msg + '

'; + if (passhint != null) passhint = EscapeHtml(passhint); + if (obj.args.minify && !req.query.nominify) { // Try to server the minified version if we can. 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) { // 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 { // 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 }); } /*