mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-02-12 11:01:52 +00:00
Fixed Duo 2FA security.
This commit is contained in:
parent
5da849063b
commit
f80ba62cfc
3 changed files with 113 additions and 24 deletions
|
@ -3645,14 +3645,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||||
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
|
if ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)) return; // If this account is settings locked, return here.
|
||||||
|
|
||||||
// Check input
|
// Check input
|
||||||
if (typeof command.enabled != 'boolean') return;
|
if ((typeof command.enabled != 'boolean') || (command.enabled != false)) return;
|
||||||
|
|
||||||
// See if we really need to change the state
|
// 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;
|
if ((command.enabled === false) && (user.otpduo == null)) return;
|
||||||
|
|
||||||
// Change the duo 2FA of this user
|
// Change the duo 2FA of this user
|
||||||
if (command.enabled === true) { user.otpduo = {}; } else { delete user.otpduo; }
|
delete user.otpduo;
|
||||||
parent.db.SetUser(user);
|
parent.db.SetUser(user);
|
||||||
ws.send(JSON.stringify({ action: 'otpduo', success: true, enabled: command.enabled })); // Report success
|
ws.send(JSON.stringify({ action: 'otpduo', success: true, enabled: command.enabled })); // Report success
|
||||||
|
|
||||||
|
|
|
@ -12887,9 +12887,15 @@
|
||||||
function account_manageAuthDuo() {
|
function account_manageAuthDuo() {
|
||||||
if (xxdialogMode || ((features2 & 0x20000000) == 0)) return;
|
if (xxdialogMode || ((features2 & 0x20000000) == 0)) return;
|
||||||
var duoU2Fenabled = ((userinfo.otpduo == 1));
|
var duoU2Fenabled = ((userinfo.otpduo == 1));
|
||||||
setDialogMode(2, "Duo Authentication", 1, function () {
|
if (duoU2Fenabled == false) {
|
||||||
if (duoU2Fenabled != Q('duo2facheck').checked) { meshserver.send({ action: 'otpduo', enabled: Q('duo2facheck').checked }); }
|
setDialogMode(2, "Duo Authentication", 3, function () {
|
||||||
}, "When enabled, on each login, you will be given the option to use Duo for added security." + '<br /><br /><label><input id=duo2facheck type=checkbox ' + (duoU2Fenabled?'checked':'') + '/>' + "Enable Duo two-factor authentication." + '</label>');
|
window.location.href = '/add-duo?rurl=' + encodeURIComponentEx(window.location.href) + ((urlargs.key)?('&key=' + urlargs.key):'');
|
||||||
|
}, "Confirm enabling of Duo 2FA security. Once enabled you will be given the option to use Due security at login. Click ok to go thru the steps to enable Duo.");
|
||||||
|
} else {
|
||||||
|
setDialogMode(2, "Duo Authentication", 3, function () {
|
||||||
|
meshserver.send({ action: 'otpduo', enabled: false });
|
||||||
|
}, "Confirm disabling Duo security.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function account_manageAuthApp() {
|
function account_manageAuthApp() {
|
||||||
|
|
120
webserver.js
120
webserver.js
|
@ -1226,7 +1226,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
sec.duostate = client.generateState();
|
sec.duostate = client.generateState();
|
||||||
req.session.e = parent.encryptSessionData(sec);
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
parent.debug('web', 'Redirecting user ' + user._id + ' to Duo');
|
parent.debug('web', 'Redirecting user ' + user._id + ' to Duo');
|
||||||
res.redirect(client.createAuthUrl(user._id, sec.duostate));
|
res.redirect(client.createAuthUrl(user._id.split('/')[2], sec.duostate));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6951,13 +6951,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup Duo callback if needed
|
// Setup Duo HTTP handlers if supported
|
||||||
if ((typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) {
|
if ((typeof domain.duo2factor == 'object') && (typeof domain.duo2factor.integrationkey == 'string') && (typeof domain.duo2factor.secretkey == 'string') && (typeof domain.duo2factor.apihostname == 'string')) {
|
||||||
|
// Duo authentication handler
|
||||||
obj.app.get(url + 'auth-duo', function (req, res){
|
obj.app.get(url + 'auth-duo', function (req, res){
|
||||||
var domain = getDomain(req);
|
var domain = getDomain(req);
|
||||||
const sec = parent.decryptSessionData(req.session.e);
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
if (req.query.state !== sec.duostate) {
|
if ((req.query.state !== sec.duostate) || (req.query.duo_code == null)) {
|
||||||
// The state returned from Duo IS NOT the same as what was in the session, so must fail
|
// 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.');
|
parent.debug('web', 'handleRootRequest: Duo 2FA state failed.');
|
||||||
req.session.loginmode = 1;
|
req.session.loginmode = 1;
|
||||||
req.session.messageid = 117; // Invalid security check
|
req.session.messageid = 117; // Invalid security check
|
||||||
|
@ -6975,21 +6976,76 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
apiHost: domain.duo2factor.apihostname,
|
apiHost: domain.duo2factor.apihostname,
|
||||||
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
|
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('?key=' + domain.loginkey) : '')
|
||||||
});
|
});
|
||||||
client.exchangeAuthorizationCodeFor2FAResult(req.query.duo_code, userid).then(function (data) {
|
client.exchangeAuthorizationCodeFor2FAResult(req.query.duo_code, userid.split('/')[2]).then(function (data) {
|
||||||
parent.debug('web', 'handleRootRequest: Duo 2FA auth ok.');
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
req.session.userid = userid;
|
if ((sec != null) && (sec.duoconfig == 1)) {
|
||||||
delete req.session.currentNode;
|
// Duo 2FA exchange success
|
||||||
req.session.ip = req.clientIp; // Bind this session to the IP address of the request
|
parent.debug('web', 'handleRootRequest: Duo 2FA configuration success.');
|
||||||
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 });
|
// Enable Duo for this user
|
||||||
res.redirect(domain.url + getQueryPortion(req));
|
var user = obj.users[userid];
|
||||||
|
if (user.otpduo == null) {
|
||||||
|
user.otpduo = {};
|
||||||
|
db.SetUser(user);
|
||||||
|
|
||||||
|
// 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: obj.CloneSafeUser(user), action: 'accountchange', msgid: 160, msg: "Enabled 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.DispatchEvent(targets, obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
delete sec.duoconfig;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
var url = req.session.duorurl;
|
||||||
|
delete req.session.duorurl;
|
||||||
|
res.redirect(url ? url : domain.url); // Redirect back to the user's original page
|
||||||
|
} else {
|
||||||
|
// Duo 2FA exchange success
|
||||||
|
parent.debug('web', 'handleRootRequest: Duo 2FA authorization success.');
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
obj.parent.authLog('https', 'Accepted Duo authentication for ' + userid + ' from ' + req.clientIp + ':' + req.connection.remotePort, { useragent: req.headers['user-agent'], sessionid: req.session.x });
|
||||||
|
res.redirect(domain.url + getQueryPortion(req));
|
||||||
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
// Duo 2FA exchange failed
|
console.log('err', err);
|
||||||
console.log('err',err);
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
parent.debug('web', 'handleRootRequest: Duo 2FA exchange authorization code failed.');
|
if ((sec != null) && (sec.duoconfig == 1)) {
|
||||||
req.session.loginmode = 1;
|
// Duo 2FA exchange success
|
||||||
req.session.messageid = 117; // Invalid security check
|
parent.debug('web', 'handleRootRequest: Duo 2FA configuration failed.');
|
||||||
res.redirect(domain.url + getQueryPortion(req));
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
delete sec.duoconfig;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
var url = req.session.duorurl;
|
||||||
|
delete req.session.duorurl;
|
||||||
|
res.redirect(url ? url : domain.url); // Redirect back to the user's original page
|
||||||
|
} else {
|
||||||
|
// Duo 2FA exchange failed
|
||||||
|
parent.debug('web', 'handleRootRequest: Duo 2FA authorization failed.');
|
||||||
|
|
||||||
|
// Clear the Duo state
|
||||||
|
delete sec.duostate;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
|
||||||
|
req.session.loginmode = 1;
|
||||||
|
req.session.messageid = 117; // Invalid security check
|
||||||
|
res.redirect(domain.url + getQueryPortion(req));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Login failed
|
// Login failed
|
||||||
|
@ -6998,6 +7054,34 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Configure Duo handler
|
||||||
|
obj.app.get(url + 'add-duo', function (req, res) {
|
||||||
|
var domain = getDomain(req);
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
|
||||||
|
if (req.session.userid == null) {
|
||||||
|
res.sendStatus(404);
|
||||||
|
} else {
|
||||||
|
// Redirect to Duo here
|
||||||
|
const duo = require('@duosecurity/duo_universal');
|
||||||
|
const client = new duo.Client({
|
||||||
|
clientId: domain.duo2factor.integrationkey,
|
||||||
|
clientSecret: domain.duo2factor.secretkey,
|
||||||
|
apiHost: domain.duo2factor.apihostname,
|
||||||
|
redirectUrl: obj.generateBaseURL(domain, req) + 'auth-duo' + (domain.loginkey != null ? ('&key=' + domain.loginkey) : '')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup the Duo configuration
|
||||||
|
if (req.query.rurl) { req.session.duorurl = req.query.rurl; } // Set Duo return URL
|
||||||
|
const sec = parent.decryptSessionData(req.session.e);
|
||||||
|
sec.duostate = client.generateState();
|
||||||
|
sec.duoconfig = 1;
|
||||||
|
req.session.e = parent.encryptSessionData(sec);
|
||||||
|
parent.debug('web', 'Redirecting user ' + req.session.userid + ' to Duo');
|
||||||
|
res.redirect(client.createAuthUrl(req.session.userid.split('/')[2], sec.duostate));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server redirects
|
// Server redirects
|
||||||
|
|
Loading…
Reference in a new issue