Completed email 2FA.

This commit is contained in:
Ylian Saint-Hilaire 2020-03-14 15:03:50 -07:00
parent 70e93f0c0f
commit 8a47379599
5 changed files with 1246 additions and 1142 deletions

View File

@ -1999,6 +1999,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (command.resetNextLogin === true) { chguser.passchange = -1; } else { chguser.passchange = Math.floor(Date.now() / 1000); }
delete chguser.passtype; // Remove the password type if one was present.
if (command.removeMultiFactor == true) {
if (chguser.otpekey) { delete chguser.otpekey; }
if (chguser.otpsecret) { delete chguser.otpsecret; }
if (chguser.otphkeys) { delete chguser.otphkeys; }
if (chguser.otpkeys) { delete chguser.otpkeys; }
@ -2984,11 +2985,33 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
}
break;
}
case 'otpemail':
{
// Check input
if (typeof command.enabled != 'boolean') return;
// See if we really need to change the state
if ((command.enabled === true) && (user.otpekey != null)) return;
if ((command.enabled === false) && (user.otpekey == null)) return;
// Change the email 2FA of this user
if (command.enabled === true) { user.otpekey = {}; } else { delete user.otpekey; }
parent.db.SetUser(user);
ws.send(JSON.stringify({ action: 'otpemail', success: true, enabled: command.enabled })); // Report success
// Notify change
var targets = ['*', 'server-users', user._id];
if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: command.enabled ? "Enabled email two-factor authentication." :"Disabled email two-factor authentication.", domain: domain.id };
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent.parent.DispatchEvent(targets, obj, event);
break;
}
case 'otpauth-request':
{
// Check is 2-step login is supported
// Check if 2-step login is supported
const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
if (twoStepLoginSupported) {
// Request a one time password to be setup
@ -3002,7 +3025,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
case 'otpauth-setup':
{
// Check is 2-step login is supported
// Check if 2-step login is supported
const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
if (twoStepLoginSupported) {
// Perform the one time password setup
@ -3030,7 +3053,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
case 'otpauth-clear':
{
// Check is 2-step login is supported
// Check if 2-step login is supported
const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
if (twoStepLoginSupported) {
// Clear the one time password secret
@ -3053,7 +3076,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
case 'otpauth-getpasswords':
{
// Check is 2-step login is supported
// Check if 2-step login is supported
const twoStepLoginSupported = ((parent.parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.nousers !== true));
if (twoStepLoginSupported == false) break;

File diff suppressed because it is too large Load Diff

View File

@ -252,6 +252,7 @@
<div id="p3AccountActions">
<p><strong>Account Security</strong></p>
<div style="margin-left:9px;margin-bottom:8px">
<div id="manageEmail2FA" style="margin-top:5px;display:none"><a onclick="account_manageAuthEmail()" style="cursor:pointer">Manage email authentication</a></div>
<div id="manageAuthApp" style="margin-top:5px;display:none"><a onclick="account_manageAuthApp()" style="cursor:pointer">Manage authenticator app</a></div>
<div id="manageOtp" style="margin-top:5px;display:none"><a onclick="account_manageOtp(0)" style="cursor:pointer">Manage backup codes</a></div>
</div>
@ -666,6 +667,9 @@
var t = localStorage.getItem('desktopsettings');
if (t != null) { desktopsettings = JSON.parse(t); }
applyDesktopSettings();
// Arrange the user interface
QV('manageEmail2FA', features & 0x00800000);
}
function onStateChanged(server, state, prevState, errorCode) {
@ -1215,6 +1219,14 @@
// MY ACCOUNT
//
function account_manageAuthEmail() {
if (xxdialogMode || ((features & 0x00800000) == 0)) return;
var emailU2Fenabled = ((userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
setDialogMode(2, "Email Authentication", 1, function () {
if (emailU2Fenabled != Q('email2facheck').checked) { meshserver.send({ action: 'otpemail', enabled: Q('email2facheck').checked }); }
}, "When enabled, on each login, you will be given the option to receive a login token to you email account for added security." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled ? 'checked' : '') + '/>' + "Enable email two-factor authenticaiton." + '</label>');
}
function account_manageAuthApp() {
if (xxdialogMode || ((features & 4096) == 0)) return;
if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
@ -1324,7 +1336,7 @@
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
// We are allowed, let's prompt to information
var x = addHtmlValue("Name", '<input id=dp3meshname style=width:170px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
@ -1891,7 +1903,7 @@
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
var node = getNodeFromId(nodeid);
if (node == null) { goBack(); return; }

View File

@ -301,6 +301,7 @@
<div id="p2AccountSecurity" style="display:none">
<p><strong>Account security</strong></p>
<div style="margin-left:25px">
<div id="manageEmail2FA"><div class="p2AccountActions"><span id="authEmailSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageAuthEmail()">Manage email authentication</a><br /></span></div>
<div id="manageAuthApp"><div class="p2AccountActions"><span id="authAppSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageAuthApp()">Manage authenticator app</a><br /></span></div>
<div id="manageHardwareOtp"><div class="p2AccountActions"><span id="authKeySetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageHardwareOtp(0)">Manage security keys</a><br /></span></div>
<div id="manageOtp"><div class="p2AccountActions"><span id="authCodesSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageOtp(0)">Manage backup codes</a><br /></span></div>
@ -1590,6 +1591,7 @@
// Update account actions
QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0)); // Hide Account Security if in single user mode, domain authentication to 2 factor auth not supported.
QV('manageEmail2FA', features & 0x00800000);
QV('p2AccountPassActions', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide Account Actions if in single user mode or domain authentication
//QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel
QV('p2ServerActions', siteRights & 21);
@ -1660,13 +1662,19 @@
QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
QV('manageOtp', (userinfo.otpsecret == 1) || (userinfo.otphkeys > 0));
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
QV('authAppSetupCheck', userinfo.otpsecret == 1);
QV('authKeySetupCheck', userinfo.otphkeys > 0);
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
masterUpdate(4 + 128 + 4096);
// Check if backup codes should really be enabled
if ((backupCodesWarningDone == false) && !(userinfo.otpkeys > 0) && (((userinfo.otpsecret == 1) && !(userinfo.otphkeys > 0)) || ((userinfo.otpsecret != 1) && (userinfo.otphkeys == 1)))) {
// Check if none or at least 2 factors are enabled.
var authFactorCount = 0;
if ((features & 0x00800000) && (userinfo.otpekey == 1)) { authFactorCount += 1; }
if (userinfo.otpkeys == 1) { authFactorCount += 1; }
if (userinfo.otpsecret == 1) { authFactorCount += 1; }
if (userinfo.otphkeys != null) { authFactorCount += userinfo.otphkeys; }
if ((backupCodesWarningDone == false) && (authFactorCount == 1)) {
var n = { text: "Please add two-factor backup codes. If the current factor is lost, there is not way to recover this account.", title: "Two factor authentication" };
addNotification(n);
backupCodesWarningDone = true;
@ -4743,7 +4751,7 @@
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" tab to change and verify an email address."); return; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return; }
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return; }
if (event && (event.shiftKey == true)) {
// Open the device in a different tab
@ -7689,6 +7697,14 @@
// MY ACCOUNT
//
function account_manageAuthEmail() {
if (xxdialogMode || ((features & 0x00800000) == 0)) return;
var emailU2Fenabled = ((userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
setDialogMode(2, "Email Authentication", 1, function () {
if (emailU2Fenabled != Q('email2facheck').checked) { meshserver.send({ action: 'otpemail', enabled: Q('email2facheck').checked }); }
}, "When enabled, on each login, you will be given the option to receive a login token to you email account for added security." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled?'checked':'') + '/>' + "Enable email two-factor authenticaiton." + '</label>');
}
function account_manageAuthApp() {
if (xxdialogMode || ((features & 4096) == 0)) return;
if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
@ -7928,7 +7944,7 @@
if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" tab to change and verify an email address."); return false; }
// Remind the user to add two factor authentication
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return false; }
if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return false; }
// We are allowed, let's prompt to information
var x = "Create a new device group using the options below." + '<br /><br />';

View File

@ -552,7 +552,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check if we can use OTP tokens with email
var otpemail = (parent.mailserver != null);
if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.email2factor == false)) { otpemail = false; }
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
// Check email key
if ((otpemail) && (user.otpekey != null) && (user.otpekey.d != null) && (user.otpekey.k === token)) {
@ -1625,6 +1625,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (domain.amtacmactivation) { features += 0x00100000; } // Intel AMT ACM activation/upgrade is possible
if (domain.usernameisemail) { features += 0x00200000; } // Username is email address
if (parent.mqttbroker != null) { features += 0x00400000; } // This server supports MQTT channels
if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (parent.mailserver != null)) { features += 0x00800000; } // using email for 2FA is allowed
// Create a authentication cookie
const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, obj.parent.loginCookieEncryptionKey);
@ -1721,7 +1722,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check if we can use OTP tokens with email
var otpemail = (parent.mailserver != null) && (req.session.tokenemail);
if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.email2factor == false)) { otpemail = false; }
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
// Render the login page
render(req, res, getRenderPage('login', req), getRenderArgs({ loginmode: loginmode, rootCertLink: getRootCertLink(), newAccount: newAccountsAllowed, newAccountPass: (((domain.newaccountspass == null) || (domain.newaccountspass == '')) ? 0 : 1), serverDnsName: obj.getWebServerName(domain), serverPublicPort: httpsPort, emailcheck: emailcheck, features: features, sessiontime: args.sessiontime, passRequirements: passRequirements, footer: (domain.footer == null) ? '' : domain.footer, hkey: encodeURIComponent(hardwareKeyChallenge), messageid: msgid, passhint: passhint, welcometext: domain.welcometext ? encodeURIComponent(domain.welcometext).split('\'').join('\\\'') : null, hwstate: hwstate, otpemail: otpemail }, domain));
@ -4274,7 +4275,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
delete user2.domain;
delete user2.subscriptions;
delete user2.passtype;
if ((typeof user2.otpkeys == 'object') && (user2.otpkeys != null)) { user2.otpekey = 1; } // Indicates that email 2FA is enabled.
if ((typeof user2.otpekey == 'object') && (user2.otpekey != null)) { user2.otpekey = 1; } // Indicates that email 2FA is enabled.
if ((typeof user2.otpsecret == 'string') && (user2.otpsecret != null)) { user2.otpsecret = 1; } // Indicates a time secret is present.
if ((typeof user2.otpkeys == 'object') && (user2.otpkeys != null)) { user2.otpkeys = 0; if (user.otpkeys != null) { for (var i = 0; i < user.otpkeys.keys.length; i++) { if (user.otpkeys.keys[i].u == true) { user2.otpkeys = 1; } } } } // Indicates the number of one time backup codes that are active.
if ((typeof user2.otphkeys == 'object') && (user2.otphkeys != null)) { user2.otphkeys = user2.otphkeys.length; } // Indicates the number of hardware keys setup