Partial work on mobile device 2FA.

This commit is contained in:
Ylian Saint-Hilaire 2021-04-13 19:59:10 -07:00
parent 444e9e43e0
commit 5cdfd7e0b9
4 changed files with 139 additions and 22 deletions

View File

@ -1516,6 +1516,50 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}
break;
}
case '2faauth': {
// Validate input
if ((typeof command.url != 'string') || (typeof command.approved != 'boolean') || (command.url.startsWith('2fa://') == false)) return;
// parse the URL
var url = null;
try { url = require('url').parse(command.url); } catch (ex) { }
if (url == null) return;
// For now, do nothing if authentication is not approved.
if (command.approve == false) return;
// Decode the cookie
var urlSplit = url.query.split('&c=');
if (urlSplit.length != 2) return;
const authCookie = parent.parent.decodeCookie(urlSplit[1], null, 1);
if ((authCookie == null) || (typeof authCookie.c != 'string') || (('code=' + authCookie.c) != urlSplit[0])) return;
if ((typeof authCookie.n != 'string') || (authCookie.n != obj.dbNodeKey) || (typeof authCookie.u != 'string')) return;
// Fetch the user
const user = parent.users[authCookie.u];
if (user == null) return;
// Add this device as the authentication push notification device for this user
if (authCookie.a == 'addAuth') {
// Change the user
user.otpdev = obj.dbNodeKey;
parent.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: parent.CloneSafeUser(user), action: 'accountchange', msgid: 113, msg: "Added push notification authentication device", 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);
}
// Complete 2FA checking
if (authCookie.a == 'checkAuth') {
// TODO
}
break;
}
default: {
parent.agentStats.unknownAgentActionCount++;
parent.parent.debug('agent', 'Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.');

View File

@ -2751,13 +2751,13 @@ 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 != null) { delete chguser.otpekey; }
if (chguser.otpsecret != null) { delete chguser.otpsecret; }
if (chguser.otphkeys != null) { delete chguser.otphkeys; }
if (chguser.otpkeys != null) { delete chguser.otpkeys; }
if ((chguser.otpekey != null) && (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.email2factor != false)) && (domain.mailserver != null))) { delete chguser.otpekey; }
if ((chguser.phone != null) && (parent.parent.smsserver != null)) { delete chguser.phone; }
if (command.removeMultiFactor === true) {
delete chguser.otpkeys; // One time backup codes
delete chguser.otpsecret; // OTP Google Authenticator
delete chguser.otphkeys; // FIDO keys
delete chguser.otpekey; // Email 2FA
delete chguser.phone; // SMS 2FA
delete chguser.otpdev; // Push notification 2FA
}
db.SetUser(chguser);
@ -4588,6 +4588,49 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
});
break;
}
case 'otpdev-clear':
{
// Remove the authentication push notification device
if (user.otpdev != null) {
// Change the user
user.otpdev = obj.dbNodeKey;
parent.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: parent.CloneSafeUser(user), action: 'accountchange', msgid: 114, msg: "Removed push notification authentication device", 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 'otpdev-set':
{
// Attempt to add a authentication push notification device
// This will only send a push notification to the device, the device needs to confirm for the auth device to be added.
if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid
parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
// Only allow use of devices with full rights
if ((node == null) || (visible == false) || (rights != 0xFFFFFFFF) || (node.agent == null) || (node.agent.id != 14) || (node.pmt == null)) return;
// Encode the cookie
const code = Buffer.from(user.name).toString('base64');
const authCookie = parent.parent.encodeCookie({ a: 'addAuth', c: code, u: user._id, n: node._id });
// Send out a push message to the device
var payload = { notification: { title: "MeshCentral", body: user.name + " authentication" }, data: { url: '2fa://auth?code=' + code + '&c=' + authCookie } };
var options = { priority: 'High', timeToLive: 60 }; // TTL: 1 minute
parent.parent.firebase.sendToDevice(node, payload, options, function (id, err, errdesc) {
if (err == null) {
parent.parent.debug('email', 'Successfully auth addition send push message to device ' + node.name);
} else {
parent.parent.debug('email', 'Failed auth addition push message to device ' + node.name + ', error: ' + errdesc);
}
});
});
break;
}
case 'webauthn-startregister':

View File

@ -363,6 +363,7 @@
<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="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="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>
<div class="p2AccountActions"></div><span><a href=# onclick="return account_viewPreviousLogins()">View previous logins</a><br /></span>
</div>
@ -2029,6 +2030,7 @@
QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
QV('authAppSetupCheck', userinfo.otpsecret == 1);
QV('authKeySetupCheck', userinfo.otphkeys > 0);
QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 2) != 0));
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
mainUpdate(4 + 128 + 4096);
@ -10052,6 +10054,31 @@
return false;
}
function account_managePushAuthDev() {
if (xxdialogMode || ((features2 & 2) == 0)) return;
if (userinfo.otpdev == 1) {
// Remove the 2FA device
setDialogMode(2, "Authentication Device", 3, function () { meshserver.send({ action: 'otpdev-clear' }); }, "Confirm removal of push authentication device?");
} else {
// Create a list of all mobile devices
var mobileDevices = [];
for (var i in nodes) { var node = nodes[i]; if ((node.agent != null) && (node.agent.id == 14) && (node.pmt == 1) && (GetNodeRights(node) == 0xFFFFFFFF)) { mobileDevices.push(node); } }
if (mobileDevices.length == 0) {
// No mobile devices found
setDialogMode(2, "Authentication Device", 1, null, "In order to use push notification authentication, a mobile device must be setup in your account with full rights.");
} else {
// Set a 2FA device
var x = "Select a device to register for push notification authentication. Once selected, the device will prompt for confirmation." + '<br /><br />';
var y = '<select id=d2devselect style=width:240px>';
for (var i in mobileDevices) { y += '<option value="' + mobileDevices[i]._id + '">' + EscapeHtml(mobileDevices[i].name) + '</option>'; }
y += '</select>';
x += addHtmlValue("Device", y);
setDialogMode(2, "Authentication Device", 3, function () { meshserver.send({ action: 'otpdev-set', nodeid: Q('d2devselect').value }); }, x);
}
}
return false;
}
function account_manageHardwareOtp() {
if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-hardware-manage')) { dialogclose(0); }
if (xxdialogMode || ((features & 4096) == 0)) return false;
@ -11961,7 +11988,9 @@
109: "User login attempt on locked account from {0}, {1}, {2}",
110: "Invalid user login attempt from {0}, {1}, {2}",
111: "Device requested Intel(R) AMT ACM TLS activation, FQDN: {0}",
112: "Ended messenger session \"{0}\" from {1} to {2}, {3} second(s)"
112: "Ended messenger session \"{0}\" from {1} to {2}, {3} second(s)",
113: "Added push notification authentication device",
114: "Removed push notification authentication device"
};
// Highlights the device being hovered

View File

@ -6642,6 +6642,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
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
if ((typeof user2.otpdev == 'string') && (user2.otpdev != null)) { user2.otpdev = 1; } // Indicates device for 2FA push notification
if ((typeof user2.webpush == 'object') && (user2.webpush != null)) { user2.webpush = user2.webpush.length; } // Indicates the number of web push sessions we have
return user2;
}