Skip to content

Commit

Permalink
add duo authentication support (#6609)
Browse files Browse the repository at this point in the history
Signed-off-by: si458 <simonsmith5521@gmail.com>
  • Loading branch information
si458 authored Dec 21, 2024
1 parent 59fcc0d commit e2362a0
Show file tree
Hide file tree
Showing 11 changed files with 2,161 additions and 1,893 deletions.
20 changes: 20 additions & 0 deletions meshcentral-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,26 @@
"default": true,
"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": {
"type": "boolean",
"default": true,
Expand Down
5 changes: 3 additions & 2 deletions meshcentral.js
Original file line number Diff line number Diff line change
Expand Up @@ -4188,7 +4188,7 @@ function mainStart() {
// Check if Windows SSPI, LDAP, Passport and YubiKey OTP will be used
var sspi = false;
var ldap = false;
var passport = null;
var passport = [];
var allsspi = true;
var yubikey = false;
var ssh = false;
Expand All @@ -4209,7 +4209,7 @@ function mainStart() {
if (mstsc == false) { config.domains[i].mstsc = false; }
if (config.domains[i].ssh == true) { ssh = true; }
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.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'); }
Expand All @@ -4230,6 +4230,7 @@ function mainStart() {
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].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
Expand Down
32 changes: 32 additions & 0 deletions meshuser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3628,6 +3628,36 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.parent.DispatchEvent(targets, obj, event);
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':
{
// Do not allow this command if 2FA's are locked
Expand Down Expand Up @@ -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 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 duo2fa = ((typeof domain.passwordrequirements != 'object') || (typeof domain.passwordrequirements.duo2factor == 'object'));
var authFactorCount = 0;
if (typeof user.otpsecret == 'string') { authFactorCount++; } // Authenticator time factor
if (email2fa && (user.otpekey != null)) { authFactorCount++; } // EMail factor
if (sms2fa && (user.phone != null)) { authFactorCount++; } // SMS 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 ((authFactorCount > 0) && (user.otpkeys != null)) { authFactorCount++; } // Backup keys
return authFactorCount;
Expand Down
Binary file added public/images/login/2fa-duo-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/login/2fa-duo-96.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e2362a0

Please sign in to comment.