More work on device 2FA.
This commit is contained in:
parent
48d5abca40
commit
69fd9dffe2
|
@ -339,10 +339,13 @@
|
|||
"numeric": { "type": "integer", "description": "Minimum number of numeric characters required in the password." },
|
||||
"nonalpha": { "type": "integer", "description": "Minimum number of non-alpha-numeric characters required in the password." },
|
||||
"reset": { "type": "integer", "description": "Number of days after which the user is required to change the account password." },
|
||||
"force2factor": { "type": "boolean", "description": "Requires that all accounts setup 2FA." },
|
||||
"email2factor": { "type": "boolean", "default": true, "description": "Set to false to disable email 2FA." },
|
||||
"sms2factor": { "type": "boolean", "default": true, "description": "Set to false to disable SMS 2FA." },
|
||||
"push2factor": { "type": "boolean", "default": true, "description": "Set to false to disable push notification 2FA." },
|
||||
"force2factor": { "type": "boolean", "default": false, "description": "Requires that all accounts setup 2FA." },
|
||||
"skip2factor": { "type": "string", "description": "IP addresses where 2FA login is skipped, for example: 127.0.0.1,192.168.2.0/24" },
|
||||
"oldPasswordBan": { "type": "integer", "description": "Number of old passwords the server should remember and not allow the user to switch back to." },
|
||||
"banCommonPasswords": { "type": "boolean", "description": "Uses WildLeek to block use of the 10000 most commonly used passwords." }
|
||||
"banCommonPasswords": { "type": "boolean", "default": false, "description": "Uses WildLeek to block use of the 10000 most commonly used passwords." }
|
||||
}
|
||||
},
|
||||
"twoFactorCookieDurationDays": { "type": "integer", "default": 30, "description": "Number of days that a user is allowed to remember this device for when completing 2FA. Set this to 0 to remove this option." },
|
||||
|
|
|
@ -2030,9 +2030,9 @@
|
|||
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('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0));
|
||||
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
|
||||
QV('managePushAuthDev', (features2 & 2) && (count2factoraAuths() > 0));
|
||||
QV('managePushAuthDev', (features2 & 0x40) && (count2factoraAuths() > 0));
|
||||
mainUpdate(4 + 128 + 4096);
|
||||
|
||||
// Check if none or at least 2 factors are enabled.
|
||||
|
@ -10056,7 +10056,7 @@
|
|||
}
|
||||
|
||||
function account_managePushAuthDev() {
|
||||
if (xxdialogMode || ((features2 & 2) == 0)) return;
|
||||
if (xxdialogMode || ((features2 & 0x40) == 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?");
|
||||
|
|
59
webserver.js
59
webserver.js
|
@ -952,7 +952,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
return;
|
||||
}
|
||||
|
||||
if ((req.body.hwtoken == '**push**') && push2fa) {
|
||||
// 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.
|
||||
if ((req.body.hwtoken == '**push**') && push2fa && ((domain.passwordrequirements == null) || (domain.passwordrequirements.push2factor != false))) {
|
||||
const logincodeb64 = Buffer.from(obj.common.zeroPad(getRandomSixDigitInteger(), 6)).toString('base64');
|
||||
const sessioncode = obj.crypto.randomBytes(24).toString('base64');
|
||||
|
||||
|
@ -978,43 +980,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
req.session.passhint = url;
|
||||
req.session.loginmode = '8';
|
||||
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
|
||||
|
||||
/*
|
||||
// Perform push notification to device
|
||||
const deviceCookie = parent.encodeCookie({ a: 'checkAuth', c: logincodeb64, u: user._id, n: user.otpdev, s: sessioncode });
|
||||
var payload = { notification: { title: "MeshCentral", body: "Authentication - " + logincode }, data: { url: '2fa://auth?code=' + logincodeb64 + '&c=' + deviceCookie } };
|
||||
var options = { priority: 'High', timeToLive: 60 }; // TTL: 1 minute
|
||||
parent.firebase.sendToDevice(user.otpdev, payload, options, function (id, err, errdesc) {
|
||||
if (err == null) {
|
||||
// Create a browser cookie so the browser can connect using websocket and wait for device accept/reject.
|
||||
const browserCookie = parent.encodeCookie({ a: 'waitAuth', c: logincodeb64, u: user._id, n: user.otpdev, s: sessioncode, d: domain.id });
|
||||
|
||||
// Get the HTTPS port
|
||||
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port if specified
|
||||
if (obj.args.agentport != null) { httpsPort = obj.args.agentport; } // If an agent only port is enabled, use that.
|
||||
if (obj.args.agentaliasport != null) { httpsPort = obj.args.agentaliasport; } // If an agent alias port is specified, use that.
|
||||
|
||||
// Get the agent connection server name
|
||||
var serverName = obj.getWebServerName(domain);
|
||||
if (typeof obj.args.agentaliasdns == 'string') { serverName = obj.args.agentaliasdns; }
|
||||
|
||||
// Build the connection URL. If we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
|
||||
var xdomain = (domain.dns == null) ? domain.id : '';
|
||||
if (xdomain != '') xdomain += '/';
|
||||
var url = 'wss://' + serverName + ':' + httpsPort + '/' + xdomain + '2fahold.ashx?c=' + browserCookie;
|
||||
|
||||
// Request that the login page wait for device auth
|
||||
req.session.messageid = 5; // "Notification sent." message
|
||||
req.session.passhint = logincode + '|' + url;
|
||||
req.session.loginmode = '8';
|
||||
} else {
|
||||
// Indicate the push notification failed
|
||||
req.session.messageid = 116; // "Unable to send device notification." message
|
||||
req.session.loginmode = '4';
|
||||
}
|
||||
if (direct === true) { handleRootRequestEx(req, res, domain); } else { res.redirect(domain.url + getQueryPortion(req)); }
|
||||
});
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2581,6 +2546,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
if (obj.parent.webpush != null) { features2 += 0x00000008; } // Indicates web push is enabled
|
||||
if (((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true))) { features2 += 0x00000010; } // No agent update
|
||||
if (parent.amtProvisioningServer != null) { features2 += 0x00000020; } // Intel AMT LAN provisioning server
|
||||
if (((typeof domain.passwordrequirements != 'object') || (domain.passwordrequirements.push2factor != false)) && (obj.parent.firebase != null)) { features2 += 0x00000040; } // Indicates device push notification 2FA is enabled
|
||||
|
||||
// Create a authentication cookie
|
||||
const authCookie = obj.parent.encodeCookie({ userid: dbGetFunc.user._id, domainid: domain.id, ip: req.clientIp }, obj.parent.loginCookieEncryptionKey);
|
||||
|
@ -2733,7 +2699,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
var otpsms = (parent.smsserver != null) && (req.session != null) && (req.session.tokensms == true);
|
||||
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.sms2factor == false)) { otpsms = false; }
|
||||
var otppush = (parent.firebase != null) && (req.session != null) && (req.session.tokenpush == true);
|
||||
//if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { otppush = false; }
|
||||
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { otppush = false; }
|
||||
|
||||
// See if we support two-factor trusted cookies
|
||||
var twoFactorCookieDays = 30;
|
||||
|
@ -2807,7 +2773,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
if (req.body.hwstate) {
|
||||
var cookie = obj.parent.decodeCookie(req.body.hwstate, obj.parent.loginCookieEncryptionKey, 1);
|
||||
if ((cookie != null) && (typeof cookie.u == 'string') && (cookie.d == domain.id) && (cookie.a == 'pushAuth')) {
|
||||
req.session = { userid: cookie.u, domainid: cookie.d } // Push authentication is a success, login the user
|
||||
// Push authentication is a success, login the user
|
||||
req.session = { userid: cookie.u, domainid: cookie.d }
|
||||
|
||||
// Check if we need to remember this device
|
||||
if ((req.body.remembertoken === 'on') && ((domain.twofactorcookiedurationdays == null) || (domain.twofactorcookiedurationdays > 0))) {
|
||||
var maxCookieAge = domain.twofactorcookiedurationdays;
|
||||
if (typeof maxCookieAge != 'number') { maxCookieAge = 30; }
|
||||
const twoFactorCookie = obj.parent.encodeCookie({ userid: cookie.u, expire: maxCookieAge * 24 * 60 /*, ip: req.clientIp*/ }, obj.parent.loginCookieEncryptionKey);
|
||||
res.cookie('twofactor', twoFactorCookie, { maxAge: (maxCookieAge * 24 * 60 * 60 * 1000), httpOnly: true, sameSite: 'strict', secure: true });
|
||||
}
|
||||
|
||||
handleRootRequestEx(req, res, domain);
|
||||
return;
|
||||
}
|
||||
|
@ -4295,12 +4271,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
|||
function handle2faHoldWebSocket(ws, req) {
|
||||
const domain = checkUserIpAddress(ws, req);
|
||||
if (domain == null) { return; }
|
||||
ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
|
||||
if ((typeof domain.passwordrequirements == 'object') && (domain.passwordrequirements.push2factor == false)) { ws.close(); return; } // Push 2FA is disabled
|
||||
if (typeof req.query.c !== 'string') { ws.close(); return; }
|
||||
const cookie = parent.decodeCookie(req.query.c, null, 1);
|
||||
if ((cookie == null) || (cookie.d != domain.id)) { ws.close(); return; }
|
||||
var user = obj.users[cookie.u];
|
||||
if ((user == null) || (typeof user.otpdev != 'string')) { ws.close(); return; }
|
||||
ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
|
||||
|
||||
// 2FA event subscription
|
||||
obj.parent.AddEventDispatch(['2fadev-' + cookie.s], ws);
|
||||
|
|
Loading…
Reference in New Issue