add duo authentication support (#6609)

Signed-off-by: si458 <simonsmith5521@gmail.com>
This commit is contained in:
Simon Smith 2024-12-21 13:52:54 +00:00 committed by GitHub
parent 59fcc0dbc6
commit e2362a0547
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 2161 additions and 1893 deletions

View File

@ -1664,6 +1664,26 @@
"default": true, "default": true,
"description": "Set to false to disable SMS 2FA." "description": "Set to false to disable SMS 2FA."
}, },
"duo2factor": {
"type": "object",
"properties": {
"integrationkey": {
"type": "string",
"default": "",
"description": "Integration key from Duo"
},
"secretkey": {
"type": "string",
"default": "",
"description": "Secret key from Duo"
},
"apihostname": {
"type": "string",
"default": "",
"description": "API Hostname from Duo"
}
}
},
"push2factor": { "push2factor": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,

View File

@ -4188,7 +4188,7 @@ function mainStart() {
// Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used // Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used
var sspi = false; var sspi = false;
var ldap = false; var ldap = false;
var passport = null; var passport = [];
var allsspi = true; var allsspi = true;
var yubikey = false; var yubikey = false;
var ssh = false; var ssh = false;
@ -4209,7 +4209,7 @@ function mainStart() {
if (mstsc == false) { config.domains[i].mstsc = false; } if (mstsc == false) { config.domains[i].mstsc = false; }
if (config.domains[i].ssh == true) { ssh = true; } if (config.domains[i].ssh == true) { ssh = true; }
if ((typeof config.domains[i].authstrategies == 'object')) { if ((typeof config.domains[i].authstrategies == 'object')) {
if (passport == null) { passport = ['passport','connect-flash']; } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors if (passport.length == 0) { passport = ['passport','connect-flash']; } // Passport v0.6.0 requires a patch, see https://github.com/jaredhanson/passport/issues/904 and include connect-flash here to display errors
if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); } if ((typeof config.domains[i].authstrategies.twitter == 'object') && (typeof config.domains[i].authstrategies.twitter.clientid == 'string') && (typeof config.domains[i].authstrategies.twitter.clientsecret == 'string') && (passport.indexOf('passport-twitter') == -1)) { passport.push('passport-twitter'); }
if ((typeof config.domains[i].authstrategies.google == 'object') && (typeof config.domains[i].authstrategies.google.clientid == 'string') && (typeof config.domains[i].authstrategies.google.clientsecret == 'string') && (passport.indexOf('passport-google-oauth20') == -1)) { passport.push('passport-google-oauth20'); } if ((typeof config.domains[i].authstrategies.google == 'object') && (typeof config.domains[i].authstrategies.google.clientid == 'string') && (typeof config.domains[i].authstrategies.google.clientsecret == 'string') && (passport.indexOf('passport-google-oauth20') == -1)) { passport.push('passport-google-oauth20'); }
if ((typeof config.domains[i].authstrategies.github == 'object') && (typeof config.domains[i].authstrategies.github.clientid == 'string') && (typeof config.domains[i].authstrategies.github.clientsecret == 'string') && (passport.indexOf('passport-github2') == -1)) { passport.push('passport-github2'); } if ((typeof config.domains[i].authstrategies.github == 'object') && (typeof config.domains[i].authstrategies.github.clientid == 'string') && (typeof config.domains[i].authstrategies.github.clientsecret == 'string') && (passport.indexOf('passport-github2') == -1)) { passport.push('passport-github2'); }
@ -4230,6 +4230,7 @@ function mainStart() {
if (config.domains[i].sessionrecording != null) { sessionRecording = true; } if (config.domains[i].sessionrecording != null) { sessionRecording = true; }
if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; } if ((config.domains[i].passwordrequirements != null) && (config.domains[i].passwordrequirements.bancommonpasswords == true)) { wildleek = true; }
if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; } if ((config.domains[i].newaccountscaptcha != null) && (config.domains[i].newaccountscaptcha !== false)) { captcha = true; }
if ((config.domains[i].passwordrequirements != null) && (typeof config.domains[i].passwordrequirements.duo2factor == 'object') && (passport.indexOf('@duosecurity/duo_universal') == -1)) { passport.push('@duosecurity/duo_universal'); }
} }
// Build the list of required modules // Build the list of required modules

View File

@ -3628,6 +3628,36 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.parent.DispatchEvent(targets, obj, event); parent.parent.DispatchEvent(targets, obj, event);
break; break;
} }
case 'otpduo':
{
// Do not allow this command if 2FA's are locked
if ((domain.passwordrequirements) && (domain.passwordrequirements.lock2factor == true)) return;
// Do not allow this command when logged in using a login token
if (req.session.loginToken != null) break;
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
// Check input
if (typeof command.enabled != 'boolean') return;
// See if we really need to change the state
if ((command.enabled === true) && (user.otpduo != null)) return;
if ((command.enabled === false) && (user.otpduo == null)) return;
// Change the duo 2FA of this user
if (command.enabled === true) { user.otpduo = {}; } else { delete user.otpduo; }
parent.db.SetUser(user);
ws.send(JSON.stringify({ action: 'otpduo', 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', msgid: command.enabled ? 160 : 161, msg: command.enabled ? "Enabled duo two-factor authentication." : "Disabled duo 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': case 'otpauth-request':
{ {
// Do not allow this command if 2FA's are locked // Do not allow this command if 2FA's are locked
@ -8224,11 +8254,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null)); var email2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null));
var sms2fa = ((parent.parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false))); var sms2fa = ((parent.parent.smsserver != null) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)));
var msg2fa = ((parent.parent.msgserver != null) && (parent.parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false))); var msg2fa = ((parent.parent.msgserver != null) && (parent.parent.msgserver.providers != 0) && ((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)));
var duo2fa = ((typeof domain.passwordrequirements != 'object') || (typeof domain.passwordrequirements.duo2factor == 'object'));
var authFactorCount = 0; var authFactorCount = 0;
if (typeof user.otpsecret == 'string') { authFactorCount++; } // Authenticator time factor if (typeof user.otpsecret == 'string') { authFactorCount++; } // Authenticator time factor
if (email2fa && (user.otpekey != null)) { authFactorCount++; } // EMail factor if (email2fa && (user.otpekey != null)) { authFactorCount++; } // EMail factor
if (sms2fa && (user.phone != null)) { authFactorCount++; } // SMS factor if (sms2fa && (user.phone != null)) { authFactorCount++; } // SMS factor
if (msg2fa && (user.msghandle != null)) { authFactorCount++; } // Messaging factor if (msg2fa && (user.msghandle != null)) { authFactorCount++; } // Messaging factor
if (duo2fa && (user.otpduo != null)) { authFactorCount++; } // Duo authentication factor
if (user.otphkeys != null) { authFactorCount += user.otphkeys.length; } // FIDO hardware factor if (user.otphkeys != null) { authFactorCount += user.otphkeys.length; } // FIDO hardware factor
if ((authFactorCount > 0) && (user.otpkeys != null)) { authFactorCount++; } // Backup keys if ((authFactorCount > 0) && (user.otpkeys != null)) { authFactorCount++; } // Backup keys
return authFactorCount; return authFactorCount;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because it is too large Load Diff

View File

@ -430,6 +430,7 @@
<div id="managePhoneNumber1"><div class="p2AccountActions"><span id="authPhoneNumberCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_managePhone()">Manage phone number</a><br /></span></div> <div id="managePhoneNumber1"><div class="p2AccountActions"><span id="authPhoneNumberCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_managePhone()">Manage phone number</a><br /></span></div>
<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="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="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="manageDuoApp"><div class="p2AccountActions"><span id="authDuoSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageAuthDuo()">Manage Duo authentication</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="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="managePushAuthDev"><div class="p2AccountActions"><span id="authPushAuthDevCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_managePushAuthDev()">Manage push authentication</a><br /></span></div> <div id="managePushAuthDev"><div class="p2AccountActions"><span id="authPushAuthDevCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_managePushAuthDev()">Manage push authentication</a><br /></span></div>
<div id="manageMessaging1"><div class="p2AccountActions"><span id="authMessagingCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageMessaging()">Manage messaging</a><br /></span></div> <div id="manageMessaging1"><div class="p2AccountActions"><span id="authMessagingCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageMessaging()">Manage messaging</a><br /></span></div>
@ -2357,6 +2358,7 @@
QV('authPhoneNumberCheck', (userinfo.phone != null)); QV('authPhoneNumberCheck', (userinfo.phone != null));
QV('authMessagingCheck', (userinfo.msghandle != null)); QV('authMessagingCheck', (userinfo.msghandle != null));
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true)); QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
QV('authDuoSetupCheck', (userinfo.otpduo == 1));
QV('authAppSetupCheck', userinfo.otpsecret == 1); QV('authAppSetupCheck', userinfo.otpsecret == 1);
QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0))); QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
QV('authKeySetupCheck', userinfo.otphkeys > 0); QV('authKeySetupCheck', userinfo.otphkeys > 0);
@ -12881,6 +12883,14 @@
}, "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 authentication." + '</label>'); }, "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 authentication." + '</label>');
} }
function account_manageAuthDuo() {
if (xxdialogMode || ((features & 0x00800000) == 0)) return;
var duoU2Fenabled = ((userinfo.otpduo == 1));
setDialogMode(2, "Duo Authentication", 1, function () {
if (duoU2Fenabled != Q('duo2facheck').checked) { meshserver.send({ action: 'otpduo', enabled: Q('duo2facheck').checked }); }
}, "When enabled, on each login, you will be given the option to login using Duo for added security." + '<br /><br /><label><input id=duo2facheck type=checkbox ' + (duoU2Fenabled?'checked':'') + '/>' + "Enable Duo two-factor authentication." + '</label>');
}
function account_manageAuthApp() { function account_manageAuthApp() {
if (xxdialogMode || ((features & 4096) == 0)) return; if (xxdialogMode || ((features & 4096) == 0)) return;
if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); } if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
@ -15242,7 +15252,9 @@
156: "Verified messaging account of user {0}", 156: "Verified messaging account of user {0}",
157: "Removed messaging account of user {0}", 157: "Removed messaging account of user {0}",
158: "Displaying alert box, title=\"{0}\", message=\"{1}\"", 158: "Displaying alert box, title=\"{0}\", message=\"{1}\"",
159: "Device Powered On" 159: "Device Powered On",
160: "Enabled Duo two-factor authentication",
161: "Disabled Duo two-factor authentication"
}; };
var eventsShortMessageId = { var eventsShortMessageId = {
@ -15534,7 +15546,7 @@
if (userdomain != '') { username += ', <span style=color:#26F>' + userdomain + '</span>'; } if (userdomain != '') { username += ', <span style=color:#26F>' + userdomain + '</span>'; }
} }
if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || ((user.phone != null) && (features & 0x04000000))) { username += ' <img src="images/key12.png" height=12 width=11 title="' + "2nd factor authentication enabled" + '" style="margin-top:2px" />'; } if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || (user.otpduo == 1) || ((user.phone != null) && (features & 0x04000000))) { username += ' <img src="images/key12.png" height=12 width=11 title="' + "2nd factor authentication enabled" + '" style="margin-top:2px" />'; }
if (user.phone != null) { username += ' <img src="images/phone12.png" height=12 width=7 title="' + "Verified phone number" + '" style="margin-top:2px" />'; } if (user.phone != null) { username += ' <img src="images/phone12.png" height=12 width=7 title="' + "Verified phone number" + '" style="margin-top:2px" />'; }
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" />'; } if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" />'; }
if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' <img src="images/messaging12.png" height=12 width=12 title="' + "Verified messaging account" + '" style="margin-top:2px" />'; } if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' <img src="images/messaging12.png" height=12 width=12 title="' + "Verified messaging account" + '" style="margin-top:2px" />'; }
@ -16717,12 +16729,13 @@
} }
var multiFactor = 0; var multiFactor = 0;
if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpekey > 0)) { if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpekey > 0) || (user.otpduo > 0)) {
multiFactor = 1; multiFactor = 1;
var factors = []; var factors = [];
if (user.otpsecret > 0) { factors.push("Authentication App"); } if (user.otpsecret > 0) { factors.push("Authentication App"); }
if (user.otphkeys > 0) { factors.push("Security Key"); } if (user.otphkeys > 0) { factors.push("Security Key"); }
if (user.otpekey > 0) { factors.push("Email"); } if (user.otpekey > 0) { factors.push("Email"); }
if (user.otpduo > 0) { factors.push("Duo"); }
if (user.otpkeys > 0) { factors.push("Backup Codes"); } if (user.otpkeys > 0) { factors.push("Backup Codes"); }
if (user.otpdev > 0) { factors.push("Device Push"); } if (user.otpdev > 0) { factors.push("Device Push"); }
if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); } if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); }

View File

@ -590,6 +590,12 @@
id="authAppSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# id="authAppSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=#
onclick="return account_manageAuthApp()">Manage authenticator onclick="return account_manageAuthApp()">Manage authenticator
app</a><br /></span> app</a><br /></span>
</div>
<div id="manageDuoApp">
<div class="p2AccountActions"><span
id="authDuoSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=#
onclick="return account_manageAuthDuo()">Manage Duo
authentication</a><br /></span>
</div> </div>
<div id="manageHardwareOtp"> <div id="manageHardwareOtp">
<div class="p2AccountActions"><span <div class="p2AccountActions"><span
@ -3018,6 +3024,7 @@
QV('authPhoneNumberCheck', (userinfo.phone != null)); QV('authPhoneNumberCheck', (userinfo.phone != null));
QV('authMessagingCheck', (userinfo.msghandle != null)); QV('authMessagingCheck', (userinfo.msghandle != null));
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true)); QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
QV('authDuoSetupCheck', (userinfo.otpduo == 1));
QV('authAppSetupCheck', userinfo.otpsecret == 1); QV('authAppSetupCheck', userinfo.otpsecret == 1);
QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0))); QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
QV('authKeySetupCheck', userinfo.otphkeys > 0); QV('authKeySetupCheck', userinfo.otphkeys > 0);
@ -13994,6 +14001,15 @@
}); });
} }
function account_manageAuthDuo() {
if (xxdialogMode || ((features & 0x00800000) == 0)) return;
var duoU2Fenabled = ((userinfo.otpduo == 1));
setModalContent('xxAddAgent', 'Duo Authentication', "When enabled, on each login, you will be given the option to login using Duo for added security." + '<br /><br /><label><input id=duo2facheck type=checkbox class="form-check-input me-2" ' + (duoU2Fenabled ? 'checked' : '') + '/>' + "Enable Duo two-factor authentication." + '</label>');
showModal('xxAddAgentModal', 'idx_dlgOkButton', function () {
if (duoU2Fenabled != Q('duo2facheck').checked) { meshserver.send({ action: 'otpduo', enabled: Q('duo2facheck').checked }); }
});
}
function account_manageAuthApp() { function account_manageAuthApp() {
if (xxdialogMode || ((features & 4096) == 0)) return; if (xxdialogMode || ((features & 4096) == 0)) return;
if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); } if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
@ -16578,7 +16594,9 @@
156: "Verified messaging account of user {0}", 156: "Verified messaging account of user {0}",
157: "Removed messaging account of user {0}", 157: "Removed messaging account of user {0}",
158: "Displaying alert box, title=\"{0}\", message=\"{1}\"", 158: "Displaying alert box, title=\"{0}\", message=\"{1}\"",
159: "Device Powered On" 159: "Device Powered On",
160: "Enabled Duo two-factor authentication",
161: "Disabled Duo two-factor authentication"
}; };
var eventsShortMessageId = { var eventsShortMessageId = {
@ -16881,7 +16899,7 @@
if (userdomain != '') { username += ', <span style=color:#26F>' + userdomain + '</span>'; } if (userdomain != '') { username += ', <span style=color:#26F>' + userdomain + '</span>'; }
} }
if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || ((user.phone != null) && (features & 0x04000000))) { username += ' <i class="fa-solid fa-key" title="' + "2nd factor authentication enabled" + '" style="margin-top:2px"></i>'; } if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || (user.otpduo == 1) || ((user.phone != null) && (features & 0x04000000))) { username += ' <i class="fa-solid fa-key" title="' + "2nd factor authentication enabled" + '" style="margin-top:2px"></i>'; }
if (user.phone != null) { username += ' <i class="fa-solid fa-mobile-screen" title="' + "Verified phone number" + '" style="margin-top:2px"></i>'; } if (user.phone != null) { username += ' <i class="fa-solid fa-mobile-screen" title="' + "Verified phone number" + '" style="margin-top:2px"></i>'; }
if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" />'; } if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" />'; }
if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' <i class="fa-solid fa-comment fa-xs" title="' + "Verified messaging account" + '" style="margin-top:2px" />'; } if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' <i class="fa-solid fa-comment fa-xs" title="' + "Verified messaging account" + '" style="margin-top:2px" />'; }
@ -18129,12 +18147,13 @@
} }
var multiFactor = 0; var multiFactor = 0;
if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpekey > 0)) { if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpekey > 0) || (user.otpduo > 0)) {
multiFactor = 1; multiFactor = 1;
var factors = []; var factors = [];
if (user.otpsecret > 0) { factors.push("Authentication App"); } if (user.otpsecret > 0) { factors.push("Authentication App"); }
if (user.otphkeys > 0) { factors.push("Security Key"); } if (user.otphkeys > 0) { factors.push("Security Key"); }
if (user.otpekey > 0) { factors.push("Email"); } if (user.otpekey > 0) { factors.push("Email"); }
if (user.otpduo > 0) { factors.push("Duo"); }
if (user.otpkeys > 0) { factors.push("Backup Codes"); } if (user.otpkeys > 0) { factors.push("Backup Codes"); }
if (user.otpdev > 0) { factors.push("Device Push"); } if (user.otpdev > 0) { factors.push("Device Push"); }
if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); } if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); }

View File

@ -193,6 +193,7 @@
<input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken(1)" /> <input style="display:none;float:right" id=emailKeyButton type=button value="Email" onclick="useEmailToken(1)" />
<input style="display:none;float:right" id=smsKeyButton type=button value="SMS" onclick="useSMSToken(1)" /> <input style="display:none;float:right" id=smsKeyButton type=button value="SMS" onclick="useSMSToken(1)" />
<input style="display:none;float:right" id=msgKeyButton type=button value="Messaging" onclick="useMsgToken(1)" /> <input style="display:none;float:right" id=msgKeyButton type=button value="Messaging" onclick="useMsgToken(1)" />
<input style="display:none;float:right" id=duoKeyButton type=button value="Duo" onclick="useDuoToken(1)" />
</div> </div>
</td> </td>
</tr> </tr>
@ -221,6 +222,7 @@
<input style="display:none;float:right" id=emailKeyButton2 type=button value="Email" onclick="useEmailToken(2)" /> <input style="display:none;float:right" id=emailKeyButton2 type=button value="Email" onclick="useEmailToken(2)" />
<input style="display:none;float:right" id=smsKeyButton2 type=button value="SMS" onclick="useSMSToken(2)" /> <input style="display:none;float:right" id=smsKeyButton2 type=button value="SMS" onclick="useSMSToken(2)" />
<input style="display:none;float:right" id=msgKeyButton2 type=button value="Messaging" onclick="useMsgToken(2)" /> <input style="display:none;float:right" id=msgKeyButton2 type=button value="Messaging" onclick="useMsgToken(2)" />
<input style="display:none;float:right" id=duoKeyButton2 type=button value="Duo" onclick="useDuoToken(2)" />
</div> </div>
</td> </td>
</tr> </tr>
@ -337,6 +339,7 @@
var webPageFullScreen = true; var webPageFullScreen = true;
var nightMode = (getstore('_nightMode', '0') == '1'); var nightMode = (getstore('_nightMode', '0') == '1');
var publicKeyCredentialRequestOptions = null; var publicKeyCredentialRequestOptions = null;
var otpduo = (decodeURIComponent('{{{otpduo}}}') === 'true');
var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true'); var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true'); var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true'); var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
@ -474,6 +477,7 @@
QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6)); QV('emailKeyButton', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6)); QV('smsKeyButton', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('msgKeyButton', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6)); QV('msgKeyButton', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('duoKeyButton', otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6));
// If hardware key is an option, trigger it now // If hardware key is an option, trigger it now
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); } if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
@ -487,6 +491,7 @@
QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6)); QV('emailKeyButton2', otpemail && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('smsKeyButton2', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6)); QV('smsKeyButton2', otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('msgKeyButton2', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6)); QV('msgKeyButton2', otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6));
QV('duoKeyButton2', otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6));
// If hardware key is an option, trigger it now // If hardware key is an option, trigger it now
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); } if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
@ -617,6 +622,18 @@
} }
} }
function useDuoToken(panelAction) {
if (panelAction == 1) {
Q('hwtokenInput').value = '**duo**';
QE('tokenOkButton', true);
Q('tokenOkButton').click();
} else if (panelAction == 2) {
Q('resetHwtokenInput').value = '**duo**';
QE('resetTokenOkButton', true);
Q('resetTokenOkButton').click();
}
}
function showPassHint(e) { function showPassHint(e) {
messagebox("Password Hint", passhint); messagebox("Password Hint", passhint);
haltEvent(e); haltEvent(e);

View File

@ -223,6 +223,7 @@
<img id=msgKeyButton src="images/login/2fa-messaging-48.png" srcset="images/login/2fa-messaging-96.png 2x" title="Messaging" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(1)" /> <img id=msgKeyButton src="images/login/2fa-messaging-48.png" srcset="images/login/2fa-messaging-96.png 2x" title="Messaging" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(1)" />
<img id=emailKeyButton src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(1)" /> <img id=emailKeyButton src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(1)" />
<img id=pushKeyButton src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(1)" /> <img id=pushKeyButton src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(1)" />
<img id=duoKeyButton src="images/login/2fa-duo-48.png" srcset="images/login/2fa-duo-96.png 2x" title="Duo Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useDuoToken(1)" />
</div> </div>
</td> </td>
</tr> </tr>
@ -258,6 +259,7 @@
<img id=msgKeyButton2 src="images/login/2fa-msg-48.png" srcset="images/login/2fa-msg-96.png 2x" title="SMS" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(2)" /> <img id=msgKeyButton2 src="images/login/2fa-msg-48.png" srcset="images/login/2fa-msg-96.png 2x" title="SMS" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useMsgToken(2)" />
<img id=emailKeyButton2 src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(2)" /> <img id=emailKeyButton2 src="images/login/2fa-mail-48.png" srcset="images/login/2fa-mail-96.png 2x" title="Email" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useEmailToken(2)" />
<img id=pushKeyButton2 src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(2)" /> <img id=pushKeyButton2 src="images/login/2fa-push-48.png" srcset="images/login/2fa-push-96.png 2x" title="Device Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="usePushToken(2)" />
<img id=duoKeyButton2 src="images/login/2fa-duo-48.png" srcset="images/login/2fa-duo-96.png 2x" title="Duo Authentication" loading="lazy" width="48" height="48" style="display:none;margin-left:3px;margin-right:3px;border-radius:3px;box-shadow:2px 2px 5px black;cursor:pointer;background-color:#FFF" onclick="useDuoToken(2)" />
</div> </div>
</td> </td>
</tr> </tr>
@ -393,6 +395,7 @@
var welcomeText = decodeURIComponent('{{{welcometext}}}'); var welcomeText = decodeURIComponent('{{{welcometext}}}');
var currentpanel = 0; var currentpanel = 0;
var publicKeyCredentialRequestOptions = null; var publicKeyCredentialRequestOptions = null;
var otpduo = (decodeURIComponent('{{{otpduo}}}') === 'true');
var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true'); var otpemail = (decodeURIComponent('{{{otpemail}}}') === 'true');
var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true'); var otpsms = (decodeURIComponent('{{{otpsms}}}') === 'true');
var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true'); var otpmsg = (decodeURIComponent('{{{otpmsg}}}') === 'true');
@ -547,12 +550,14 @@
var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6); var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6); var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6); var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
var duokey = otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6);
QV('securityKeyButton', twofakey); QV('securityKeyButton', twofakey);
QV('emailKeyButton', emailkey); QV('emailKeyButton', emailkey);
QV('smsKeyButton', smskey); QV('smsKeyButton', smskey);
QV('msgKeyButton', msgkey); QV('msgKeyButton', msgkey);
QV('pushKeyButton', pushkey); QV('pushKeyButton', pushkey);
QV('2farow', twofakey || emailkey || smskey || msgkey || pushkey); QV('duoKeyButton', duokey);
QV('2farow', twofakey || emailkey || smskey || msgkey || pushkey || duokey);
// If hardware key is an option, trigger it now // If hardware key is an option, trigger it now
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); } if (autofido && twofakey) { setTimeout(function () { useSecurityKey(1); }, 300); }
@ -566,12 +571,14 @@
var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6); var smskey = otpsms && (messageid != 2) && (messageid != 4) && (messageid != 6);
var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6); var msgkey = otpmsg && (messageid != 2) && (messageid != 4) && (messageid != 6);
var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6); var pushkey = otppush && (messageid != 2) && (messageid != 4) && (messageid != 6);
var duokey = otpduo && (messageid != 2) && (messageid != 4) && (messageid != 6);
QV('securityKeyButton2', twofakey); QV('securityKeyButton2', twofakey);
QV('emailKeyButton2', emailkey); QV('emailKeyButton2', emailkey);
QV('smsKeyButton2', smskey); QV('smsKeyButton2', smskey);
QV('msgKeyButton2', msgkey); QV('msgKeyButton2', msgkey);
QV('pushKeyButton', pushkey); QV('pushKeyButton2', pushkey);
QV('2farow2', twofakey || emailkey || smskey || msgkey || pushkey); QV('duoKeyButton2', duokey);
QV('2farow2', twofakey || emailkey || smskey || msgkey || pushkey || duokey);
// If hardware key is an option, trigger it now // If hardware key is an option, trigger it now
if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); } if (autofido && twofakey) { setTimeout(function () { useSecurityKey(2); }, 300); }
@ -723,6 +730,18 @@
} }
} }
function useDuoToken(panelAction) {
if (panelAction == 1) {
Q('hwtokenInput').value = '**duo**';
QE('tokenOkButton', true);
Q('tokenOkButton').click();
} else if (panelAction == 2) {
Q('resetHwtokenInput').value = '**duo**';
QE('resetTokenOkButton', true);
Q('resetTokenOkButton').click();
}
}
function showPassHint(e) { function showPassHint(e) {
messagebox("Password Hint", passhint); messagebox("Password Hint", passhint);
haltEvent(e); haltEvent(e);

View File

@ -918,7 +918,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null)); var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null));
// Check if a 2nd factor is present // Check if a 2nd factor is present
return ((parent.config.settings.no2factorauth !== true) && (msg2fa || sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) || ((user.otphkeys != null) && (user.otphkeys.length > 0)))); return ((parent.config.settings.no2factorauth !== true) && (msg2fa || sms2fa || (user.otpsecret != null) || ((user.email != null) && (user.emailVerified == true) && (domain.mailserver != null) && (user.otpekey != null)) || (user.otpduo != null) || ((user.otphkeys != null) && (user.otphkeys.length > 0))));
} }
// Check the 2-step auth token // Check the 2-step auth token
@ -1162,6 +1162,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null)); var sms2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.sms2factor != false)) && (parent.smsserver != null) && (user.phone != null));
var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null)); var msg2fa = (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.msg2factor != false)) && (parent.msgserver != null) && (parent.msgserver.providers != 0) && (user.msghandle != null));
var push2fa = ((parent.firebase != null) && (user.otpdev != null)); var push2fa = ((parent.firebase != null) && (user.otpdev != null));
var duo2fa = (((typeof domain.passwordrequirements != 'object') || (typeof domain.passwordrequirements.duo2factor == 'object')) && (user.otpduo != null));
// Check if two factor can be skipped // Check if two factor can be skipped
const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions); const twoFactorSkip = checkUserOneTimePasswordSkip(domain, user, req, loginOptions);
@ -1211,6 +1212,24 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
return; return;
} }
if ((req.body.hwtoken == '**duo**') && duo2fa) {
// Redirect to duo here
const duo = require('@duosecurity/duo_universal');
const client = new duo.Client({
clientId: domain.passwordrequirements.duo2factor.integrationkey,
clientSecret: domain.passwordrequirements.duo2factor.secretkey,
apiHost: domain.passwordrequirements.duo2factor.apihostname,
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
});
// Decrypt any session data
const sec = parent.decryptSessionData(req.session.e);
sec.duostate = client.generateState();
req.session.e = parent.encryptSessionData(sec);
parent.debug('web', 'Redirecting user ' + user._id + ' to Duo');
res.redirect(client.createAuthUrl(user._id, sec.duostate));
return;
}
// Handle device push notification 2FA request // Handle device push notification 2FA request
// We create a browser cookie, send it back and when the browser connects it's web socket, it will trigger the push notification. // We create a browser cookie, send it back and when the browser connects it's web socket, it will trigger the push notification.
if ((req.body.hwtoken == '**push**') && push2fa && ((domain.passwordrequirements == null) || (domain.passwordrequirements.push2factor != false))) { if ((req.body.hwtoken == '**push**') && push2fa && ((domain.passwordrequirements == null) || (domain.passwordrequirements.push2factor != false))) {
@ -1274,6 +1293,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
if ((user.phone != null) && (parent.smsserver != null)) { req.session.tsms = 1; } if ((user.phone != null) && (parent.smsserver != null)) { req.session.tsms = 1; }
if ((user.msghandle != null) && (parent.msgserver != null) && (parent.msgserver.providers != 0)) { req.session.tmsg = 1; } if ((user.msghandle != null) && (parent.msgserver != null) && (parent.msgserver.providers != 0)) { req.session.tmsg = 1; }
if ((user.otpdev != null) && (parent.firebase != null)) { req.session.tpush = 1; } if ((user.otpdev != null) && (parent.firebase != null)) { req.session.tpush = 1; }
if ((user.otpduo != null)) { req.session.tduo = 1; }
req.session.e = parent.encryptSessionData({ tuserid: userid, tuser: xusername, tpass: xpassword }); req.session.e = parent.encryptSessionData({ tuserid: userid, tuser: xusername, tpass: xpassword });
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); } if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
}, randomWaitTime); }, randomWaitTime);
@ -3320,6 +3340,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
// Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5). // Check if we can use OTP tokens with email. We can't use email for 2FA password recovery (loginmode 5).
var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string')); var otpemail = (loginmode != 5) && (domain.mailserver != null) && (req.session != null) && ((req.session.temail === 1) || (typeof req.session.temail == 'string'));
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; } if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.email2factor == false)) { otpemail = false; }
var otpduo = (req.session != null) && (req.session.tduo === 1);
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.duo2factor == false)) { otpduo = false; }
var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1); var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tsms === 1);
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; } if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
var otpmsg = (parent.msgserver != null) && (req.session != null) && (req.session.tmsg === 1); var otpmsg = (parent.msgserver != null) && (req.session != null) && (req.session.tmsg === 1);
@ -3402,6 +3424,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
welcomePictureFullScreen: ((typeof domain.welcomepicturefullscreen == 'boolean') ? domain.welcomepicturefullscreen : false), welcomePictureFullScreen: ((typeof domain.welcomepicturefullscreen == 'boolean') ? domain.welcomepicturefullscreen : false),
hwstate: hwstate, hwstate: hwstate,
otpemail: otpemail, otpemail: otpemail,
otpduo: otpduo,
otpsms: otpsms, otpsms: otpsms,
otpmsg: otpmsg, otpmsg: otpmsg,
otppush: otppush, otppush: otppush,
@ -5851,6 +5874,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
} }
}; };
// generate the server url
obj.generateBaseURL = function (domain, req) {
var serverName = obj.getWebServerName(domain, req);
var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
var xdomain = (domain.dns == null) ? domain.id : '';
if (xdomain != '') xdomain += '/';
return ('https://' + serverName + ':' + httpsPort + '/' + xdomain);
}
// Get the web server hostname. This may change if using a domain with a DNS name. // Get the web server hostname. This may change if using a domain with a DNS name.
obj.getWebServerName = function (domain, req) { obj.getWebServerName = function (domain, req) {
if (domain.dns != null) return domain.dns; if (domain.dns != null) return domain.dns;
@ -6918,6 +6950,55 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
} }
} }
// // Setup Duo callback if needed
if ((typeof domain.passwordrequirements == 'object') && (typeof domain.passwordrequirements.duo2factor == 'object')) {
obj.app.get(url + 'auth-duo', function (req, res){
var domain = getDomain(req);
const sec = parent.decryptSessionData(req.session.e);
if (req.query.state !== sec.duostate) {
// the state returned from Duo IS NOT the same as what was in the session, so must fail!
parent.debug('web', 'handleRootRequest: duo 2fa state failed!');
req.session.loginmode = 1;
req.session.messageid = 117; // Invalid security check
res.redirect(domain.url + getQueryPortion(req)); // redirect back to main page
return;
} else {
// User credentials are stored in session, just check again and get userid
obj.authenticate(sec.tuser, sec.tpass, domain, function (err, userid, passhint, loginOptions) {
if ((userid != null) && (err == null)) {
// Login data correct, now exchange authorization code for 2fa
const duo = require('@duosecurity/duo_universal');
const client = new duo.Client({
clientId: domain.passwordrequirements.duo2factor.integrationkey,
clientSecret: domain.passwordrequirements.duo2factor.secretkey,
apiHost: domain.passwordrequirements.duo2factor.apihostname,
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
});
client.exchangeAuthorizationCodeFor2FAResult(req.query.duo_code, userid).then(function (data) {
parent.debug('web', 'handleRootRequest: duo 2fa auth ok.');
req.session.userid = userid;
delete req.session.currentNode;
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
setSessionRandom(req);
obj.parent.authLog('https', 'Accepted duo authentication for ' + userid + ' from ' + req.clientIp + ' port ' + req.connection.remotePort, { useragent: req.headers['user-agent'], sessionid: req.session.x });
res.redirect(domain.url + getQueryPortion(req));
}).catch(function (err) {
// Duo 2FA exchange failed, so must fail!
console.log('err',err);
parent.debug('web', 'handleRootRequest: duo 2fa exchange authorization code failed!.');
req.session.loginmode = 1;
req.session.messageid = 117; // Invalid security check
res.redirect(domain.url + getQueryPortion(req));
});
} else {
// Login failed
handleRootRequestEx(req, res, domain, direct);
}
});
}
});
}
// Server redirects // Server redirects
if (parent.config.domains[i].redirects) { for (var j in parent.config.domains[i].redirects) { if (j[0] != '_') { obj.app.get(url + j, obj.handleDomainRedirect); } } } if (parent.config.domains[i].redirects) { for (var j in parent.config.domains[i].redirects) { if (j[0] != '_') { obj.app.get(url + j, obj.handleDomainRedirect); } } }
@ -8787,6 +8868,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
delete user2.otpsms; delete user2.otpsms;
delete user2.otpmsg; delete user2.otpmsg;
if ((typeof user2.otpekey == 'object') && (user2.otpekey != 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.otpduo == 'object') && (user2.otpduo != null)) { user2.otpduo = 1; } // Indicates that duo 2FA is enabled.
if ((typeof user2.otpsecret == 'string') && (user2.otpsecret != null)) { user2.otpsecret = 1; } // Indicates a time secret is present. 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.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 if ((typeof user2.otphkeys == 'object') && (user2.otphkeys != null)) { user2.otphkeys = user2.otphkeys.length; } // Indicates the number of hardware keys setup
@ -9290,8 +9372,27 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
} catch (ex) { return { browserStr: browser, osStr: os } } } catch (ex) { return { browserStr: browser, osStr: os } }
} }
// Return the query string portion of the URL, the ? and anything after. // Return the query string portion of the URL, the ? and anything after BUT remove secret keys from authentication providers
function getQueryPortion(req) { var s = req.url.indexOf('?'); if (s == -1) { if (req.body && req.body.urlargs) { return req.body.urlargs; } return ''; } return req.url.substring(s); } function getQueryPortion(req) {
var removeKeys = ['duo_code', 'state']; // Keys to remove
var s = req.url.indexOf('?');
if (s == -1) {
if (req.body && req.body.urlargs) {
return req.body.urlargs;
}
return '';
}
var queryString = req.url.substring(s + 1);
var params = queryString.split('&');
var filteredParams = [];
for (var i = 0; i < params.length; i++) {
var key = params[i].split('=')[0];
if (removeKeys.indexOf(key) === -1) {
filteredParams.push(params[i]);
}
}
return (filteredParams.length > 0 ? ('?' + filteredParams.join('&')) : '');
}
// Generate a random Intel AMT password // Generate a random Intel AMT password
function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); } function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }