From 632d190d23749b127ad7a5611c7cd4b39d1ce60d Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 5 Feb 2019 19:07:12 -0800 Subject: [PATCH] Added one time passwords for user 2-factor login. --- meshuser.js | 121 ++++++++++++++------------------ package.json | 2 +- views/default-min.handlebars | 2 +- views/default-mobile.handlebars | 43 ++++++++++-- views/default.handlebars | 65 +++++++++++++---- views/login-mobile.handlebars | 9 ++- views/login.handlebars | 9 ++- webserver.js | 74 +++++++++++-------- 8 files changed, 202 insertions(+), 123 deletions(-) diff --git a/meshuser.js b/meshuser.js index 5280b23b..8b246474 100644 --- a/meshuser.js +++ b/meshuser.js @@ -119,7 +119,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use obj.ws.domainid = domain.id; // Create a new session id for this user. - require('crypto').randomBytes(20, function (err, randombuf) { + obj.parent.crypto.randomBytes(20, function (err, randombuf) { obj.ws.sessionId = user._id + '/' + randombuf.toString('hex'); // Add this web socket session to session list @@ -206,16 +206,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use try { ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: serverinfo })); } catch (ex) { } // Send user information to web socket, this is the first thing we send - var userinfo = obj.common.Clone(obj.parent.users[user._id]); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - if (userinfo.otpsecret) { userinfo.otpsecret = 1; } - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo })); } catch (ex) { } + try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(obj.parent.users[user._id]) })); } catch (ex) { } // We are all set, start receiving data ws._socket.resume(); @@ -441,7 +432,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use break; } case 'showconfig': { - r = JSON.stringify(obj.parent.parent.config, null, 4); + var config = obj.common.Clone(obj.parent.parent.config); + if (config.settings) { + if (config.settings.configkey) { config.settings.configkey = '(present)'; } + if (config.settings.sessionkey) { config.settings.sessionkey = '(present)'; } + if (config.settings.dbencryptkey) { config.settings.dbencryptkey = '(present)'; } + } + r = JSON.stringify(removeAllUnderScore(config), null, 4); break; } default: { // This is an unknown command, return an error message @@ -507,16 +504,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var docs = []; for (i in obj.parent.users) { if ((obj.parent.users[i].domain == domain.id) && (obj.parent.users[i].name != '~')) { - var userinfo = obj.common.Clone(obj.parent.users[i]); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - delete userinfo.otpsecret; - docs.push(userinfo); + docs.push(obj.parent.CloneSafeUser(obj.parent.users[i])); } } try { ws.send(JSON.stringify({ action: 'users', users: docs, tag: command.tag })); } catch (ex) { } @@ -541,20 +529,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use obj.parent.db.SetUser(user); // Event the change - var userinfo = obj.common.Clone(user); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - delete userinfo.otpsecret; - var message = { etype: 'user', username: userinfo.name, account: userinfo, action: 'accountchange', domain: domain.id }; + var message = { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(user), action: 'accountchange', domain: domain.id }; if (oldemail != null) { - message.msg = 'Changed email of user ' + userinfo.name + ' from ' + oldemail + ' to ' + user.email; + message.msg = 'Changed email of user ' + user.name + ' from ' + oldemail + ' to ' + user.email; } else { - message.msg = 'Set email of user ' + userinfo.name + ' to ' + user.email; + message.msg = 'Set email of user ' + user.name + ' to ' + user.email; } obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, message); @@ -651,11 +630,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use newuser.salt = salt; newuser.hash = hash; obj.db.SetUser(newuser); - var newuser2 = obj.common.Clone(newuser); - if (newuser2.subscriptions) { delete newuser2.subscriptions; } - if (newuser2.salt) { delete newuser2.salt; } - if (newuser2.hash) { delete newuser2.hash; } - obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: newusername, account: newuser2, 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; @@ -674,16 +649,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (change == 1) { obj.db.SetUser(chguser); obj.parent.parent.DispatchEvent([chguser._id], obj, 'resubscribe'); - var userinfo = obj.common.Clone(chguser); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - delete userinfo.otpsecret; - obj.parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: userinfo, action: 'accountchange', msg: 'Account changed: ' + command.name, domain: domain.id }); + obj.parent.parent.DispatchEvent(['*', 'server-users', user._id, chguser._id], obj, { etype: 'user', username: user.name, account: obj.parent.CloneSafeUser(chguser), action: 'accountchange', msg: 'Account changed: ' + command.name, domain: domain.id }); } if ((chguser.siteadmin) && (chguser.siteadmin != 0xFFFFFFFF) && (chguser.siteadmin & 32)) { obj.parent.parent.DispatchEvent([chguser._id], obj, 'close'); // Disconnect all this user's sessions @@ -1429,16 +1395,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use ws.send(JSON.stringify({ action: 'otpauth-setup', success: true })); // Report success // Notify change - var userinfo = obj.common.Clone(user); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - if (userinfo.otpsecret) { userinfo.otpsecret = 1; } - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo })); } catch (ex) { } + try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } } else { ws.send(JSON.stringify({ action: 'otpauth-setup', success: false })); // Report fail } @@ -1456,16 +1413,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use obj.parent.db.SetUser(user); // Notify change - var userinfo = obj.common.Clone(user); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - if (userinfo.otpsecret) { userinfo.otpsecret = 1; } - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo })); } catch (ex) { } + 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 } else { ws.send(JSON.stringify({ action: 'otpauth-clear', success: false })); // Report fail @@ -1473,6 +1421,35 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } break; } + case 'otpauth-getpasswords': + { + // Check is 2-step login is supported + const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.parent.certificates.CommonName != 'un-configured') && (obj.args.lanonly !== true) && (obj.args.nousers !== true)); + + // Perform a sub-action + var actionTaken = false; + if (command.subaction == 1) { // Generate a new set of tokens + var randomNumbers = []; + for (var i = 0; i < 10; i++) { + var v; // TODO: This random generation does not produce equal changes for all values. FIX IT! + do { v = (obj.parent.crypto.randomBytes(4).readUInt32BE(0) % 100000000); } while (randomNumbers.indexOf(v) >= 0); + randomNumbers.push(v); + } + user.otpkeys = { keys: [] }; + for (var i = 0; i < 10; i++) { user.otpkeys.keys[i] = { p: randomNumbers[i], u: true } } + actionTaken = true; + } else if (command.subaction == 2) { // Clear all tokens + actionTaken = (user.otpkeys != null); + user.otpkeys = null; + } + + // Save the changed user + if (actionTaken) { obj.parent.db.SetUser(user); } + + // Return one time passwords for this user + if (twoStepLoginSupported && user.otpsecret) { ws.send(JSON.stringify({ action: 'otpauth-getpasswords', passwords: user.otpkeys?user.otpkeys.keys:null })); } + break; + } case 'getNotes': { // Argument validation @@ -1622,6 +1599,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use function splitArgs(str) { var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi; do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null); return myArray; } function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; } + function removeAllUnderScore(obj) { + if (typeof obj != 'object') return obj; + for (var i in obj) { if (i.startsWith('_')) { delete obj[i]; } else if (typeof obj[i] == 'object') { removeAllUnderScore(obj[i]); } } + return obj; + } + // Parse arguments string array into an object function parseArgs(argv) { var results = { '_': [] }, current = null; diff --git a/package.json b/package.json index 15e8e732..722fec1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.7-h", + "version": "0.2.7-i", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index bb08c0bc..3e8bd402 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default-mobile.handlebars b/views/default-mobile.handlebars index d4f118d8..4dc621b2 100644 --- a/views/default-mobile.handlebars +++ b/views/default-mobile.handlebars @@ -241,6 +241,7 @@ +
Change email address
Change password
Delete account
@@ -649,6 +650,13 @@ xdr.send(); } + function updateSelf() { + QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); + QV('otpAuth', ((features & 4096) != 0) && (userinfo.otpsecret != 1)); + QV('otpAuthRemove', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); + QV('manageOtp', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); + } + function onMessage(server, message) { switch (message.action) { case 'serverinfo': { @@ -661,9 +669,7 @@ userinfo = message.userinfo; QH('p3userName', userinfo.name); //updateSiteAdmin(); - QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); - QV('otpAuth', ((features & 4096) != 0) && (userinfo.otpsecret != 1)); - QV('otpAuthRemove', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); + updateSelf(); break; } case 'users': { @@ -744,6 +750,29 @@ setDialogMode(2, "Remove 2-Step Login", 1, null, message.success ? "2-step login activation removed. You can reactivate this feature at any time." : "2-step login activation removal failed. Try again."); break; } + case 'otpauth-getpasswords': { + if (xxdialogMode) return; + var x = "One time tokens can be used as secondary authentication. Generate a set, print them and keep them in a safe place."; + x += "
"; + if (message.passwords) { + var j = 0; + for (var i in message.passwords) { + if (++j % 2) { x += ''; } + var p = '' + message.passwords[i].p; + while (p.length < 8) { p = '0' + p; } + if (message.passwords[i].u === true) { x += '
' + p.substring(0, 4) + ' ' + p.substring(4); } else { x += '' + p.substring(0, 4) + ' ' + p.substring(4); + ''; } + } + } else { + x += '
No Active Tokens'; + } + x += "

"; + x += "
"; + x += ""; + if (message.passwords != null) { x += ""; } + x += "

"; + setDialogMode(2, "One-Time Passwords", 8, null, x, 'otpauth-manage'); + break; + } case 'event': { /* if (!message.event.nolog) { @@ -762,7 +791,7 @@ if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); } userinfo = message.event.account; if (oldsiteadmin != newsiteadmin) updateSiteAdmin(); - QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); + updateSelf(); } break; } @@ -1012,6 +1041,12 @@ setDialogMode(2, "Remove 2-Step Login", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of 2-step login?"); } + function account_manageOtp(action) { + if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-manage')) { dialogclose(0); } + if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return; + meshserver.send({ action: 'otpauth-getpasswords', subaction: action }); + } + function account_showVerifyEmail() { if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return; var x = "Click ok to send a verification mail to:
" + EscapeHtml(userinfo.email) + "
Please wait a few minute to receive the verification."; diff --git a/views/default.handlebars b/views/default.handlebars index 37558eeb..bb22e0ae 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -251,6 +251,7 @@ + Change email address
Change password
Delete account
@@ -357,10 +358,10 @@

My Server

Server actions

- Download server backup
- Restore server with backup
- Check server version
- Show server error log
+

Download server backup
+
Restore server with backup
+
Check server version
+
Show server error log


Server Statistics

@@ -1107,7 +1108,7 @@ // Update account actions QV('p2AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide Account Actions if in single user mode or domain authentication QV('p2ServerActions', siteRights & 21); - QV('LeftMenuMyServer', siteRights & 21); + QV('LeftMenuMyServer', siteRights & 21); // 16 + 4 + 1 QV('MainMenuMyServer', siteRights & 21); QV('p2ServerActionsBackup', siteRights & 1); QV('p2ServerActionsRestore', siteRights & 4); @@ -1132,6 +1133,9 @@ } meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) }); QV('p2deleteall', userinfo.siteadmin == 0xFFFFFFFF); + QV('ServerConsole', userinfo.siteadmin === 0xFFFFFFFF); + if ((xxcurrentView == 115) && (userinfo.siteadmin != 0xFFFFFFFF)) { go(6); } + if ((xxcurrentView == 6) && ((userinfo.siteadmin & 21) == 0)) { go(1); } // If we are site administrator, register to get server statistics if ((siteRights & 21) != 0) { meshserver.send({ action: 'serverstats', interval: 10000 }); } @@ -1162,6 +1166,14 @@ } } + function updateSelf() { + QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); + QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); + QV('otpAuth', ((features & 4096) != 0) && (userinfo.otpsecret != 1)); + QV('otpAuthRemove', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); + QV('manageOtp', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); + } + function onMessage(server, message) { switch (message.action) { case 'serverstats': { @@ -1180,10 +1192,7 @@ case 'userinfo': { userinfo = message.userinfo; updateSiteAdmin(); - QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); - QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); - QV('otpAuth', ((features & 4096) != 0) && (userinfo.otpsecret != 1)); - QV('otpAuthRemove', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); + updateSelf(); break; } case 'users': { @@ -1406,6 +1415,29 @@ setDialogMode(2, "Remove 2-Step Login", 1, null, message.success ? "2-step login activation removed. You can reactivate this feature at any time." : "2-step login activation removal failed. Try again."); break; } + case 'otpauth-getpasswords': { + if (xxdialogMode) return; + var x = "One time tokens can be used as secondary authentication. Generate a set, print them and keep them in a safe place."; + x += "
"; + if (message.passwords) { + var j = 0; + for (var i in message.passwords) { + if (++j % 2) { x += ''; } + var p = '' + message.passwords[i].p; + while (p.length < 8) { p = '0' + p; } + if (message.passwords[i].u === true) { x += '
' + p.substring(0, 4) + ' ' + p.substring(4); } else { x += '' + p.substring(0, 4) + ' ' + p.substring(4); + ''; } + } + } else { + x += '
No Active Tokens'; + } + x += "

"; + x += "
"; + x += ""; + if (message.passwords != null) { x += ""; } + x += "

"; + setDialogMode(2, "Manage One Time Passwords", 8, null, x, 'otpauth-manage'); + break; + } case 'event': { if (!message.event.nolog) { events.unshift(message.event); @@ -1423,8 +1455,7 @@ if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); } userinfo = message.event.account; if (oldsiteadmin != newsiteadmin) updateSiteAdmin(); - QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); - QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); + updateSelf(); } if (users == null) break; users[message.event.account._id] = message.event.account; @@ -4612,7 +4643,7 @@ var h = ''; if (f.t < 3) { var right = '', title = ''; - h = "
 " + right + "
" + shortname + "
"; + h = "
 " + right + "
" + shortname + "
"; } else { var link = shortname; if (f.s > 0) { link = "" + shortname + ""; } @@ -5208,6 +5239,12 @@ setDialogMode(2, "Remove 2-Step Login", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of 2-step login?"); } + function account_manageOtp(action) { + if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-manage')) { dialogclose(0); } + if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return; + meshserver.send({ action: 'otpauth-getpasswords', subaction: action }); + } + function account_showVerifyEmail() { if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return; var x = "Click ok to send a verification mail to:
" + EscapeHtml(userinfo.email) + "
Please wait a few minute to receive the verification."; @@ -5679,7 +5716,7 @@ var h = ''; if (f.t < 3 || f.t == 4) { var right = (f.t == 1 || f.t == 4)?p5getQuotabar(f):'', title = ''; - h = "
 " + right + "
" + shortname + "
"; + h = "
 " + right + "
" + shortname + "
"; } else { var link = shortname; var publiclink = ''; @@ -6403,7 +6440,7 @@ var h = ''; if (f.t < 3) { var title = ''; - h = ""; + h = ""; } else { var link = shortname; //if (f.s > 0) { link = "" + shortname + ""; } diff --git a/views/login-mobile.handlebars b/views/login-mobile.handlebars index 5b1f8c04..228e2f4d 100644 --- a/views/login-mobile.handlebars +++ b/views/login-mobile.handlebars @@ -148,7 +148,7 @@ - +
Login token:
@@ -328,7 +328,12 @@ return true; } - function checkToken() { QE('tokenOkButton', Q('tokenInput').value.length == 6); } + function checkToken() { + var t1 = Q('tokenInput').value; + var t2 = t1.replace(/\D/g, ''); + if (t1 != t2) { Q('tokenInput').value = t2; } + QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8)); + } // // POPUP DIALOG diff --git a/views/login.handlebars b/views/login.handlebars index 6418d7fd..27e1a730 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -221,7 +221,7 @@ - +
Login token:
@@ -423,7 +423,12 @@ return true; } - function checkToken() { QE('tokenOkButton', Q('tokenInput').value.length == 6); } + function checkToken() { + var t1 = Q('tokenInput').value; + var t2 = t1.replace(/\D/g, ''); + if (t1 != t2) { Q('tokenInput').value = t2; } + QE('tokenOkButton', (Q('tokenInput').value.length == 6) || (Q('tokenInput').value.length == 8)); + } // // POPUP DIALOG diff --git a/webserver.js b/webserver.js index ca5d2c37..144de51a 100644 --- a/webserver.js +++ b/webserver.js @@ -355,13 +355,29 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { const otplib = require('otplib') otplib.authenticator.options = { window: 6 }; // Set +/- 3 minute window if (twoStepLoginSupported && user.otpsecret && ((typeof (req.body.token) != 'string') || ((tokenValid = otplib.authenticator.check(req.body.token, user.otpsecret)) !== true))) { - // 2-step auth is required, but the token is not present or not valid. - if (tokenValid === false) { req.session.error = 'Invalid token, try again.'; } - req.session.loginmode = '4'; - req.session.tokenusername = xusername; - req.session.tokenpassword = xpassword; - res.redirect(domain.url); - return; + // Failed OTP, check user's one time passwords + if ((req.body.token != null) && (user.otpkeys != null) && (user.otpkeys.keys != null)) { + var found = null; + var tokenNumber = parseInt(req.body.token); + for (var i = 0; i < user.otpkeys.keys.length; i++) { if ((tokenNumber === user.otpkeys.keys[i].p) && (user.otpkeys.keys[i].u === true)) { user.otpkeys.keys[i].u = false; found = i; } } + if (found == null) { + // 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.'; } + req.session.loginmode = '4'; + req.session.tokenusername = xusername; + req.session.tokenpassword = xpassword; + res.redirect(domain.url); + return; + } + } else { + // 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.'; } + req.session.loginmode = '4'; + req.session.tokenusername = xusername; + req.session.tokenpassword = xpassword; + res.redirect(domain.url); + return; + } } // Save login time @@ -464,7 +480,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { 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: user, action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id }); + 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); } @@ -540,22 +556,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.db.SetUser(user); // Event the change - var userinfo = obj.common.Clone(user); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - if (userinfo.otpsecret) { userinfo.otpsecret = 1; } - obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: userinfo.name, account: userinfo, action: 'accountchange', msg: 'Verified email of user ' + EscapeHtml(user.name) + ' (' + EscapeHtml(userinfo.email) + ')', domain: domain.id }); + obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: obj.CloneSafeUser(user), action: 'accountchange', msg: 'Verified email of user ' + EscapeHtml(user.name) + ' (' + EscapeHtml(userinfo.email) + ')', domain: domain.id }); // Send the confirmation page res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', message: 'Verified email ' + EscapeHtml(user.email) + ' for user account ' + EscapeHtml(user.name) + '. Go to login page.' }); // Send a notification - obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: 'Email verified:
' + EscapeHtml(userinfo.email) + '.', nolog: 1 }); + obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: 'Email verified:
' + EscapeHtml(user.email) + '.', nolog: 1 }); } }); } @@ -581,16 +588,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.db.SetUser(userinfo); // Event the change - userinfo = obj.common.Clone(userinfo); - delete userinfo.hash; - delete userinfo.passhint; - delete userinfo.salt; - delete userinfo.type; - delete userinfo.domain; - delete userinfo.subscriptions; - delete userinfo.passtype; - if (userinfo.otpsecret) { userinfo.otpsecret = 1; } - obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: userinfo.name, account: userinfo, action: 'accountchange', msg: 'Password reset for user ' + EscapeHtml(user.name), domain: domain.id }); + obj.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: userinfo.name, account: obj.CloneSafeUser(userinfo), action: 'accountchange', msg: 'Password reset for user ' + EscapeHtml(user.name), domain: domain.id }); // Send the new password res.render(obj.path.join(obj.parent.webViewsPath, 'message'), { title: domain.title, title2: domain.title2, title3: 'Account Verification', message: '
Password for account ' + EscapeHtml(user.name) + ' has been reset to:
' + EscapeHtml(newpass) + '
Login and go to the \"My Account\" tab to update your password. Go to login page.' }); @@ -760,7 +758,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (usercount == 0) { user2.siteadmin = 0xFFFFFFFF; } // If this is the first user, give the account site admin. obj.users[req.session.userid] = user2; obj.db.SetUser(user2); - obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: req.connection.user, account: user2, action: 'accountcreate', msg: 'Domain account created, user ' + req.connection.user, domain: domain.id }); + obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: req.connection.user, account: obj.CloneSafeUser(user2), action: 'accountcreate', msg: 'Domain account created, user ' + req.connection.user, domain: domain.id }); } } } @@ -2289,6 +2287,22 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } }; + // Clone a safe version of a user object, remove everything that is secret. + obj.CloneSafeUser = function(user) { + if (typeof user != 'object') { return user; } + var user2 = obj.common.Clone(user); + delete user2.hash; + delete user2.passhint; + delete user2.salt; + delete user2.type; + delete user2.domain; + delete user2.subscriptions; + delete user2.passtype; + if (user2.otpsecret) { user2.otpsecret = 1; } // Indicates a time secret is present. + if (user2.otpkeys) { user2.otpkeys = 1; } // Indicates a set of one time passwords are present. + return user2; + } + // Return true if a mobile browser is detected. // This code comes from "http://detectmobilebrowsers.com/" and was modified, This is free and unencumbered software released into the public domain. For more information, please refer to the http://unlicense.org/ function isMobileBrowser(req) {