Partial support for 2-step login

This commit is contained in:
Ylian Saint-Hilaire 2019-01-15 13:51:07 -08:00
parent 2be76dd220
commit bd693123e8
4 changed files with 106 additions and 9 deletions

View File

@ -207,8 +207,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Send user information to web socket, this is the first thing we send // Send user information to web socket, this is the first thing we send
var userinfo = obj.common.Clone(obj.parent.users[user._id]); var userinfo = obj.common.Clone(obj.parent.users[user._id]);
delete userinfo.salt;
delete userinfo.hash; 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: userinfo })); } catch (ex) { }
// We are all set, start receiving data // We are all set, start receiving data
@ -454,6 +460,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
delete userinfo.domain; delete userinfo.domain;
delete userinfo.subscriptions; delete userinfo.subscriptions;
delete userinfo.passtype; delete userinfo.passtype;
delete userinfo.otpsecret;
docs.push(userinfo); docs.push(userinfo);
} }
} }
@ -486,6 +493,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
delete userinfo.domain; delete userinfo.domain;
delete userinfo.subscriptions; delete userinfo.subscriptions;
delete userinfo.passtype; 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: userinfo.name, account: userinfo, action: 'accountchange', domain: domain.id };
if (oldemail != null) { if (oldemail != null) {
message.msg = 'Changed email of user ' + userinfo.name + ' from ' + oldemail + ' to ' + user.email; message.msg = 'Changed email of user ' + userinfo.name + ' from ' + oldemail + ' to ' + user.email;
@ -619,6 +627,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
delete userinfo.domain; delete userinfo.domain;
delete userinfo.subscriptions; delete userinfo.subscriptions;
delete userinfo.passtype; 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: userinfo, action: 'accountchange', msg: 'Account changed: ' + command.name, domain: domain.id });
} }
if ((chguser.siteadmin) && (chguser.siteadmin != 0xFFFFFFFF) && (chguser.siteadmin & 32)) { if ((chguser.siteadmin) && (chguser.siteadmin != 0xFFFFFFFF) && (chguser.siteadmin & 32)) {
@ -1329,6 +1338,63 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
} }
} }
break;
}
case 'otpauth-request':
{
// Request a one time password to be setup
const otplib = require('otplib');
const secret = otplib.authenticator.generateSecret();
ws.send(JSON.stringify({ action: 'otpauth-request', secret: secret, url: otplib.authenticator.keyuri(user.name, 'MeshCentral', secret) }));
break;
}
case 'otpauth-setup':
{
// Perform the one time password setup
if (require('otplib').authenticator.check(command.token, command.secret) === true) {
// Token is valid, activate 2-step login on this account.
user.otpsecret = command.secret;
obj.parent.db.SetUser(user);
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) { }
} else {
ws.send(JSON.stringify({ action: 'otpauth-setup', success: false })); // Report fail
}
break;
}
case 'otpauth-clear':
{
// Clear the one time password secret
if (user.otpsecret) {
delete user.otpsecret;
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) { }
ws.send(JSON.stringify({ action: 'otpauth-clear', success: true })); // Report success
} else {
ws.send(JSON.stringify({ action: 'otpauth-clear', success: false })); // Report fail
}
break; break;
} }
case 'getNotes': case 'getNotes':

View File

@ -41,6 +41,7 @@
"multiparty": "^4.2.1", "multiparty": "^4.2.1",
"nedb": "^1.8.0", "nedb": "^1.8.0",
"node-forge": "^0.7.6", "node-forge": "^0.7.6",
"otplib": "^10.0.1",
"util.promisify": "^1.0.0", "util.promisify": "^1.0.0",
"ws": "^6.1.2", "ws": "^6.1.2",
"xmldom": "^0.1.27", "xmldom": "^0.1.27",

1
public/scripts/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,7 @@
<script type="text/javascript" src="scripts/agent-redir-ws-0.1.0.js"></script> <script type="text/javascript" src="scripts/agent-redir-ws-0.1.0.js"></script>
<script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0.js"></script> <script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0.js"></script>
<script type="text/javascript" src="scripts/agent-desktop-0.0.2.js"></script> <script type="text/javascript" src="scripts/agent-desktop-0.0.2.js"></script>
<script type="text/javascript" src="scripts/qrcode.min.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/charts.js"></script> <script keeplink=1 type="text/javascript" src="scripts/charts.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/filesaver.1.1.20151003.js"></script> <script keeplink=1 type="text/javascript" src="scripts/filesaver.1.1.20151003.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/ol.js"></script> <script keeplink=1 type="text/javascript" src="scripts/ol.js"></script>
@ -239,7 +240,8 @@
<p><strong><img alt="" width=150 height=103 src=images/mainaccount.jpg style=margin-bottom:10px;margin-right:20px;float:right />Account actions</strong></p> <p><strong><img alt="" width=150 height=103 src=images/mainaccount.jpg style=margin-bottom:10px;margin-right:20px;float:right />Account actions</strong></p>
<p style="margin-left:40px"> <p style="margin-left:40px">
<span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a><br /></span> <span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a><br /></span>
<span id="addTwoFactorAuth" style="display:none"><a onclick="account_addTwoFactor()" style="cursor:pointer">Add two-factor authentication</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>
<a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a><br /> <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_showChangePassword()" style="cursor:pointer">Change password</a><br />
<a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a><br /> <a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a><br />
@ -1119,7 +1121,10 @@
updateSiteAdmin(); updateSiteAdmin();
QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
//QV('addTwoFactorAuth', (userinfo.secauth != true) && ((features & 4096) != 0)); if ((features & 4096) != 0) {
QV('otpAuth', (userinfo.otpsecret != 1));
QV('otpAuthRemove', (userinfo.otpsecret == 1));
}
break; break;
} }
case 'users': { case 'users': {
@ -1308,6 +1313,26 @@
} }
break; break;
} }
case 'otpauth-request': {
if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
QH('d2optinfo', '<table style=width:380px><tr><td style=vertical-align:top>Install <a href=\"https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2\" rel=\"noreferrer noopener\" target=_blank>Google Authenticator</a> or a compatible application and scan the barcode, use <a href=\"' + message.url + '\" rel=\"noreferrer noopener\" target=_blank> this link</a> or enter the secret. Then, enter the current 6 digit token below to activate 2-Step login.<br /><br />Secret<br /><tt id=d2optsecret style=font-size:12px>' + message.secret + '</tt><br /><br /></td><td style=width:1px;vertical-align:top><a href=\"' + message.url + '\" rel=\"noreferrer noopener\" target=_blank><div id="qrcode"></div></a></td><tr><td colspan=2 style="text-align:center;border-top:1px solid black"><br />Enter the token here for 2-step login: <input type=text onkeypress=\"return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)\" onkeyup=account_addOtpCheck() onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type="text"></td></table>');
new QRCode(Q("qrcode"), { text: message.url, width: 128, height: 128, colorDark: "#000000", colorLight: "#EEE", correctLevel: QRCode.CorrectLevel.H });
QV('idx_dlgOkButton', true);
QE('idx_dlgOkButton', false);
Q('d2otpauthinput').focus();
}
break;
}
case 'otpauth-setup': {
if (xxdialogMode) return;
setDialogMode(2, "Add 2-Step Login", 1, null, message.success ? "<b style=color:green>2-step login activation successful</b>. You will now need a valid token to login again." : "<b style=color:red>2-step login activation failed</b>. Clear the secret from the application and try again. You only have a few minutes to enter the proper code.");
break;
}
case 'otpauth-clear': {
if (xxdialogMode) return;
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 'event': { case 'event': {
if (!message.event.nolog) { if (!message.event.nolog) {
events.unshift(message.event); events.unshift(message.event);
@ -5029,15 +5054,19 @@
// MY ACCOUNT // MY ACCOUNT
// //
function account_addTwoFactor() { function account_addOtp() {
if (xxdialogMode || (userinfo.secauth == true) || ((features & 4096) == 0)) return; if (xxdialogMode || (userinfo.otpsecret == 1) || ((features & 4096) == 0)) return;
var x = "Loading..."; setDialogMode(2, "Add 2-Step Login", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').innerHTML, token: Q('d2otpauthinput').value }); }, "<div id=d2optinfo>Loading...</div>", 'otpauth-request');
setDialogMode(2, "Add Two-Factor Authentication", 2, account_addTwoFactorEx, x); meshserver.send({ action: 'otpauth-request' });
meshserver.send({ action: 'secauth-request' });
} }
function account_addTwoFactorEx() { function account_addOtpCheck() {
QE('idx_dlgOkButton', Q('d2otpauthinput').value.length == 6);
}
function account_removeOtp() {
if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return;
setDialogMode(2, "Remove 2-Step Login", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of 2-step login?");
} }
function account_showVerifyEmail() { function account_showVerifyEmail() {