Added one time passwords for user 2-factor login.

This commit is contained in:
Ylian Saint-Hilaire 2019-02-05 19:07:12 -08:00
parent 173c10709e
commit dcfec7487f
8 changed files with 202 additions and 123 deletions

View File

@ -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;

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.2.7-h",
"version": "0.2.7-i",
"keywords": [
"Remote Management",
"Intel AMT",

File diff suppressed because one or more lines are too long

View File

@ -241,6 +241,7 @@
<span id="otpAuth" style="display:none"><a onclick="account_addOtp()" style="cursor:pointer">Add 2-step login</a><br /></span>
<span id="otpAuthRemove" style="display:none"><a onclick="account_removeOtp()" style="cursor:pointer">Remove 2-step login</a><br /></span>
</div>
<div id="manageOtp" style="margin-top:5px;display:none"><a onclick="account_manageOtp(0)" style="cursor:pointer">One-time passwords</a></div>
<div style="margin-top:5px"><a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a></div>
<div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a></div>
<div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a></div>
@ -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 ? "<b style=color:green>2-step login activation removed</b>. You can reactivate this feature at any time." : "<b style=color:red>2-step login activation removal failed</b>. 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 += "<div style='border-radius:6px;border: 2px dashed #888;width:100%;margin-top:8px'><div style='padding:8px;font-family:Arial, Helvetica, sans-serif;font-size:20px;font-weight:bold'><table style=width:100%;text-align:center>";
if (message.passwords) {
var j = 0;
for (var i in message.passwords) {
if (++j % 2) { x += '<tr>'; }
var p = '' + message.passwords[i].p;
while (p.length < 8) { p = '0' + p; }
if (message.passwords[i].u === true) { x += '<td>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); } else { x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>'; }
}
} else {
x += '<tr><td>No Active Tokens';
}
x += "</table></div></div><br />";
x += "<div><input type=button value='Close' onclick=setDialogMode(0) style=float:right></input>";
x += "<input type=button value='New Tokens' onclick='account_manageOtp(1);'></input>";
if (message.passwords != null) { x += "<input type=button value='Clear' onclick='account_manageOtp(2);'></input>"; }
x += "</div><br />";
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:<br /><div style=padding:8px><b>" + EscapeHtml(userinfo.email) + "</b></div>Please wait a few minute to receive the verification.";

View File

@ -251,6 +251,7 @@
<span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a><br /></span>
<span id="otpAuth" style="display:none"><a onclick="account_addOtp()" style="cursor:pointer">Add 2-step login</a><br /></span>
<span id="otpAuthRemove" style="display:none"><a onclick="account_removeOtp()" style="cursor:pointer">Remove 2-step login</a><br /></span>
<span id="manageOtp" style="display:none"><a onclick="account_manageOtp(0)" style="cursor:pointer">Manage one time passwords</a><br /></span>
<a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a><br />
<a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><br />
<a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a><br />
@ -357,10 +358,10 @@
<h1>My Server</h1>
<p id="p2ServerActions"><strong>Server actions</strong></p>
<p style="margin-left:40px">
<a id="p2ServerActionsBackup" href="/backup.zip" rel="noreferrer noopener" target="_blank" style="cursor:pointer">Download server backup</a><br />
<a id="p2ServerActionsRestore" onclick="server_showRestoreDlg()" style="cursor:pointer">Restore server with backup</a><br />
<a id="p2ServerActionsVersion" onclick="server_showVersionDlg()" style="cursor:pointer">Check server version</a><br />
<a id="p2ServerActionsErrors" onclick="server_showErrorsDlg()" style="cursor:pointer">Show server error log</a><br />
<div id="p2ServerActionsBackup" style="margin-left:40px"><a href="/backup.zip" rel="noreferrer noopener" target="_blank" style="cursor:pointer">Download server backup</a></div>
<div id="p2ServerActionsRestore" style="margin-left:40px"><a onclick="server_showRestoreDlg()" style="cursor:pointer">Restore server with backup</a></div>
<div id="p2ServerActionsVersion" style="margin-left:40px"><a onclick="server_showVersionDlg()" style="cursor:pointer">Check server version</a></div>
<div id="p2ServerActionsErrors" style="margin-left:40px"><a onclick="server_showErrorsDlg()" style="cursor:pointer">Show server error log</a></div>
</p>
<br /><strong>Server Statistics</strong><br /><br />
<div id="serverStats" style="margin-left:40px">
@ -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 ? "<b style=color:green>2-step login activation removed</b>. You can reactivate this feature at any time." : "<b style=color:red>2-step login activation removal failed</b>. 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 += "<div style='border-radius:6px;border: 2px dashed #888;width:100%;margin-top:8px'><div style='padding:8px;font-family:Arial, Helvetica, sans-serif;font-size:20px;font-weight:bold'><table style=width:100%;text-align:center>";
if (message.passwords) {
var j = 0;
for (var i in message.passwords) {
if (++j % 2) { x += '<tr>'; }
var p = '' + message.passwords[i].p;
while (p.length < 8) { p = '0' + p; }
if (message.passwords[i].u === true) { x += '<td>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); } else { x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>'; }
}
} else {
x += '<tr><td>No Active Tokens';
}
x += "</table></div></div><br />";
x += "<div><input type=button value='Close' onclick=setDialogMode(0) style=float:right></input>";
x += "<input type=button value='Generate New Tokens' onclick='account_manageOtp(1);'></input>";
if (message.passwords != null) { x += "<input type=button value='Clear Tokens' onclick='account_manageOtp(2);'></input>"; }
x += "</div><br />";
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 = "<div class=filelist file=999><input file=999 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value='" + f.nx + "'>&nbsp;<span style=float:right title=\"" + title + "\">" + right + "</span><span><div class=fileIcon" + f.t + "></div><a style=cursor:pointer onclick=p13folderset(\"" + encodeURIComponent(f.nx) + "\")>" + shortname + "</a></span></div>";
h = "<div class=filelist file=999><input file=999 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value='" + f.nx + "'>&nbsp;<span style=float:right title=\"" + title + "\">" + right + "</span><span><div class=fileIcon" + f.t + " onclick=p13folderset(\"" + encodeURIComponent(f.nx) + "\")></div><a style=cursor:pointer onclick=p13folderset(\"" + encodeURIComponent(f.nx) + "\")>" + shortname + "</a></span></div>";
} else {
var link = shortname;
if (f.s > 0) { link = "<a rel=\"noreferrer noopener\" target=\"_blank\" style=cursor:pointer onclick=\"p13downloadfile('" + encodeURIComponent(newlinkpath + '/' + name) + "','" + encodeURIComponent(name) + "'," + f.s + ")\">" + shortname + "</a>"; }
@ -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:<br /><div style=padding:8px><b>" + EscapeHtml(userinfo.email) + "</b></div>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 = "<div class=filelist file=999><input file=999 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value='" + name + "'>&nbsp;<span style=float:right title=\"" + title + "\">" + right + "</span><span><div class=fileIcon" + f.t + "></div><a style=cursor:pointer onclick=p5folderset(\"" + encodeURIComponent(f.nx) + "\")>" + shortname + "</a></span></div>";
h = "<div class=filelist file=999><input file=999 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value='" + name + "'>&nbsp;<span style=float:right title=\"" + title + "\">" + right + "</span><span><div class=fileIcon" + f.t + " onclick=p5folderset(\"" + encodeURIComponent(f.nx) + "\")></div><a style=cursor:pointer onclick=p5folderset(\"" + encodeURIComponent(f.nx) + "\")>" + shortname + "</a></span></div>";
} else {
var link = shortname;
var publiclink = '';
@ -6403,7 +6440,7 @@
var h = '';
if (f.t < 3) {
var title = '';
h = "<div class=filelist file=999><span style=float:right title=\"" + title + "\"></span><span><div class=fileIcon" + f.t + "></div>&nbsp;<a style=cursor:pointer onclick=d3folderset(\"" + encodeURIComponent(f.nx) + "\")>" + shortname + "</a></span></div>";
h = "<div class=filelist file=999><span style=float:right title=\"" + title + "\"></span><span><div class=fileIcon" + f.t + " onclick=d3folderset(\"" + encodeURIComponent(f.nx) + "\")></div>&nbsp;<a style=cursor:pointer onclick=d3folderset(\"" + encodeURIComponent(f.nx) + "\")>" + shortname + "</a></span></div>";
} else {
var link = shortname;
//if (f.s > 0) { link = "<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"downloadfile.ashx?link=" + encodeURIComponent(filetreelinkpath + '/' + f.nx) + "\">" + shortname + "</a>"; }

View File

@ -148,7 +148,7 @@
<table>
<tr>
<td align=right width=100>Login token:</td>
<td><input id=tokenInput type=text name=token maxlength=6 onkeypress="return (event.keyCode == 8) || (event.keyCode == 13) || (event.charCode >= 48 && event.charCode <= 57)" onkeyup=checkToken(event) onkeydown=checkToken(event) /></td>
<td><input id=tokenInput type=text name=token maxlength=12 onkeypress="return (event.keyCode == 8) || (event.keyCode == 13) || (event.charCode >= 48 && event.charCode <= 57)" onkeyup=checkToken(event) onkeydown=checkToken(event) /></td>
</tr>
<tr>
<td colspan=2>
@ -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

View File

@ -221,7 +221,7 @@
<table>
<tr>
<td align=right width=100>Login token:</td>
<td><input id=tokenInput type=text name=token maxlength=6 onkeypress="return (event.keyCode == 8) || (event.keyCode == 13) || (event.charCode >= 48 && event.charCode <= 57)" onkeyup=checkToken(event) onkeydown=checkToken(event) /></td>
<td><input id=tokenInput type=text name=token maxlength=12 onkeypress="return (event.keyCode == 8) || (event.keyCode == 13) || (event.charCode >= 48 && event.charCode <= 57)" onkeyup=checkToken(event) onkeydown=checkToken(event) /></td>
</tr>
<tr>
<td colspan=2>
@ -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

View File

@ -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 = '<b style=color:#8C001A>Invalid token, try again.</b>'; }
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 = '<b style=color:#8C001A>Invalid token, try again.</b>'; }
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 = '<b style=color:#8C001A>Invalid token, try again.</b>'; }
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 <b>' + EscapeHtml(user.email) + '</b> for user account <b>' + EscapeHtml(user.name) + '</b>. <a href="' + domain.url + '">Go to login page</a>.' });
// Send a notification
obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: 'Email verified:<br /><b>' + EscapeHtml(userinfo.email) + '</b>.', nolog: 1 });
obj.parent.DispatchEvent([user._id], obj, { action: 'notify', value: 'Email verified:<br /><b>' + EscapeHtml(user.email) + '</b>.', 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: '<div>Password for account <b>' + EscapeHtml(user.name) + '</b> has been reset to:</div><div style=padding:14px;font-size:18px><b>' + EscapeHtml(newpass) + '</b></div>Login and go to the \"My Account\" tab to update your password. <a href="' + domain.url + '">Go to login page</a>.' });
@ -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) {