mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-02-12 11:01:52 +00:00
More work on device 2FA.
This commit is contained in:
parent
36b5163534
commit
48d5abca40
6 changed files with 178 additions and 73 deletions
12
meshagent.js
12
meshagent.js
|
@ -1525,9 +1525,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||||
try { url = require('url').parse(command.url); } catch (ex) { }
|
try { url = require('url').parse(command.url); } catch (ex) { }
|
||||||
if (url == null) return;
|
if (url == null) return;
|
||||||
|
|
||||||
// For now, do nothing if authentication is not approved.
|
|
||||||
if (command.approve == false) return;
|
|
||||||
|
|
||||||
// Decode the cookie
|
// Decode the cookie
|
||||||
var urlSplit = url.query.split('&c=');
|
var urlSplit = url.query.split('&c=');
|
||||||
if (urlSplit.length != 2) return;
|
if (urlSplit.length != 2) return;
|
||||||
|
@ -1541,6 +1538,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||||
|
|
||||||
// Add this device as the authentication push notification device for this user
|
// Add this device as the authentication push notification device for this user
|
||||||
if (authCookie.a == 'addAuth') {
|
if (authCookie.a == 'addAuth') {
|
||||||
|
// Do nothing if authentication is not approved.
|
||||||
|
// We do not want to indicate that the remote user responded to this.
|
||||||
|
if (command.approved !== true) return;
|
||||||
|
|
||||||
// Change the user
|
// Change the user
|
||||||
user.otpdev = obj.dbNodeKey;
|
user.otpdev = obj.dbNodeKey;
|
||||||
parent.db.SetUser(user);
|
parent.db.SetUser(user);
|
||||||
|
@ -1555,8 +1556,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||||
|
|
||||||
// Complete 2FA checking
|
// Complete 2FA checking
|
||||||
if (authCookie.a == 'checkAuth') {
|
if (authCookie.a == 'checkAuth') {
|
||||||
// TODO
|
if (typeof authCookie.s != 'string') return;
|
||||||
//console.log(authCookie);
|
// Notify 2FA response
|
||||||
|
parent.parent.DispatchEvent(['2fadev-' + authCookie.s], obj, { etype: '2fadev', action: '2faresponse', domain: domain.id, nodeid: obj.dbNodeKey, code: authCookie.a, userid: user._id, approved: command.approved, sessionid: authCookie.s, nolog: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Binary file not shown.
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
@ -2032,8 +2032,7 @@
|
||||||
QV('authKeySetupCheck', userinfo.otphkeys > 0);
|
QV('authKeySetupCheck', userinfo.otphkeys > 0);
|
||||||
QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 2) != 0));
|
QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 2) != 0));
|
||||||
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
|
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
|
||||||
//QV('managePushAuthDev', (features2 & 2) && (count2factoraAuths() > 0));
|
QV('managePushAuthDev', (features2 & 2) && (count2factoraAuths() > 0));
|
||||||
QV('managePushAuthDev', false);
|
|
||||||
mainUpdate(4 + 128 + 4096);
|
mainUpdate(4 + 128 + 4096);
|
||||||
|
|
||||||
// Check if none or at least 2 factors are enabled.
|
// Check if none or at least 2 factors are enabled.
|
||||||
|
|
|
@ -284,20 +284,26 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id=waitpushpanel style="display:none">
|
<div id=waitpushpanel style="display:none">
|
||||||
<div id=message8></div>
|
<form method=post>
|
||||||
<table style="width:100%">
|
<input type=hidden name=action value=pushlogin />
|
||||||
<tr>
|
<div id=message8></div>
|
||||||
<td style="align-content:center;padding-top:10px">
|
<table style="width:100%">
|
||||||
<img src="images/login/push-150.png" srcset="images/login/push-300.png 2x" loading="lazy" width="265" height="150" />
|
<tr>
|
||||||
</td>
|
<td style="align-content:center;padding-top:10px">
|
||||||
</tr>
|
<img id="waitpushpanelimage" src="images/login/push-150.png" srcset="images/login/push-300.png 2x" style="opacity:0.3" loading="lazy" width="265" height="150" />
|
||||||
<tr>
|
</td>
|
||||||
<td style="align-content:center;padding-top:10px">
|
</tr>
|
||||||
<label id=tokenInputRememberLabel2><input id=tokenInputRemember2 name=remembertoken type=checkbox /><span id=tokenInputRememberSpan2></span></label>
|
<tr>
|
||||||
</td>
|
<td style="align-content:center;padding-top:10px">
|
||||||
</tr>
|
<label id=tokenInputRememberLabel2><input id=tokenInputRemember2 name=remembertoken type=checkbox /><span id=tokenInputRememberSpan2></span></label>
|
||||||
</table>
|
</td>
|
||||||
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr /><a onclick="return xgo(1,event);" href="#" style=cursor:pointer>Back to login</a>
|
||||||
|
<input id=pushtokenformargs name="urlargs" type="hidden" value="" />
|
||||||
|
<input id=pushtokenInput name="hwstate" type="hidden" value="" />
|
||||||
|
<input id=pushOkButton type=submit style="display:none" />
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -351,6 +357,7 @@
|
||||||
var otppush = (decodeURIComponent('{{{otppush}}}') === 'true');
|
var otppush = (decodeURIComponent('{{{otppush}}}') === 'true');
|
||||||
var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
|
var twoFactorCookieDays = parseInt('{{{twoFactorCookieDays}}}');
|
||||||
var authStrategies = '{{{authStrategies}}}'.split(',');
|
var authStrategies = '{{{authStrategies}}}'.split(',');
|
||||||
|
var websocket = null;
|
||||||
|
|
||||||
function startup() {
|
function startup() {
|
||||||
if (decodeURIComponent('{{{loginpicture}}}') == 'true') { Q('loginPicture').src = "loginlogo.png"; }
|
if (decodeURIComponent('{{{loginpicture}}}') == 'true') { Q('loginPicture').src = "loginlogo.png"; }
|
||||||
|
@ -361,13 +368,12 @@
|
||||||
// Display the right server message
|
// Display the right server message
|
||||||
var i;
|
var i;
|
||||||
var messageid = parseInt('{{{messageid}}}');
|
var messageid = parseInt('{{{messageid}}}');
|
||||||
var okmessages = ['', "If valid, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link.", "SMS sent.", "Notification sent, {0}."];
|
var okmessages = ['', "If valid, reset mail sent.", "Email sent.", "Email verification required, check your mailbox and click the confirmation link.", "SMS sent.", "Sending notification..."];
|
||||||
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later.", "Server under maintenance.", "Unable to send device notification."];
|
var failmessages = ["Unable to create account.", "Account limit reached.", "Existing account with this email address.", "Invalid account creation token.", "Username already exists.", "Password rejected, use a different one.", "Invalid email.", "Account not found.", "Invalid token, try again.", "Unable to sent email.", "Account locked.", "Access denied.", "Login failed, check username and password.", "Password change requested.", "IP address blocked, try again later.", "Server under maintenance.", "Unable to send device notification."];
|
||||||
if (messageid > 0) {
|
if (messageid > 0) {
|
||||||
var msg = '';
|
var msg = '';
|
||||||
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
|
if ((messageid < 100) && (messageid < okmessages.length)) { msg = okmessages[messageid]; }
|
||||||
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
|
else if ((messageid >= 100) && ((messageid - 100) < failmessages.length)) { msg = failmessages[messageid - 100]; }
|
||||||
if (messageid == 5) { msg = format(msg, passhint); }
|
|
||||||
if (msg != '') {
|
if (msg != '') {
|
||||||
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
|
if (messageid >= 100) { msg = ('<span class="msg error"><b style=color:#8C001A>' + msg + '<b></span><br /><br />'); } else { msg = ('<span class="msg success"><b>' + msg + '</b></span><br /><br />'); }
|
||||||
for (i = 1; i < 9; i++) { QH('message' + i, msg); }
|
for (i = 1; i < 9; i++) { QH('message' + i, msg); }
|
||||||
|
@ -403,16 +409,12 @@
|
||||||
Q('createformargs').value = xurlargs;
|
Q('createformargs').value = xurlargs;
|
||||||
Q('resetformargs').value = xurlargs;
|
Q('resetformargs').value = xurlargs;
|
||||||
Q('tokenformargs').value = xurlargs;
|
Q('tokenformargs').value = xurlargs;
|
||||||
|
Q('pushtokenformargs').value = xurlargs;
|
||||||
Q('resettokenformargs').value = xurlargs;
|
Q('resettokenformargs').value = xurlargs;
|
||||||
Q('resetpasswordformargs').value = xurlargs;
|
Q('resetpasswordformargs').value = xurlargs;
|
||||||
Q('checkemailformargs').value = xurlargs;
|
Q('checkemailformargs').value = xurlargs;
|
||||||
}
|
}
|
||||||
|
|
||||||
//var webPageFullScreen = getstore('webPageFullScreen', true);
|
|
||||||
//if (webPageFullScreen == 'false') { webPageFullScreen = false; }
|
|
||||||
//if (webPageFullScreen == 'true') { webPageFullScreen = true; }
|
|
||||||
//toggleFullScreen();
|
|
||||||
|
|
||||||
if ((features & 32) == 0) {
|
if ((features & 32) == 0) {
|
||||||
// Guard against other site's top frames (web bugs).
|
// Guard against other site's top frames (web bugs).
|
||||||
var loc = null;
|
var loc = null;
|
||||||
|
@ -442,16 +444,6 @@
|
||||||
if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); }
|
if (authStrategies.indexOf('saml') >= 0) { QV('auth-saml', true); }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display the welcome text
|
|
||||||
/*
|
|
||||||
if (welcomeText) {
|
|
||||||
QH('welcomeText', welcomeText);
|
|
||||||
} else {
|
|
||||||
QH('welcomeText', addTextLink('MeshCentral', Q('welcomeText').innerHTML, 'http://www.meshcommander.com/meshcentral2'));
|
|
||||||
}
|
|
||||||
QV('welcomeText', true);
|
|
||||||
*/
|
|
||||||
|
|
||||||
validateLogin();
|
validateLogin();
|
||||||
validateCreate();
|
validateCreate();
|
||||||
if (loginMode.length != 0) { go(parseInt(loginMode)); } else { go(1); }
|
if (loginMode.length != 0) { go(parseInt(loginMode)); } else { go(1); }
|
||||||
|
@ -487,38 +479,40 @@
|
||||||
QV('2farow2', twofakey || emailkey || smskey || pushkey);
|
QV('2farow2', twofakey || emailkey || smskey || pushkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (loginMode == '8') {
|
||||||
if (loginMode == '5') {
|
// Perform websocket connection to server to wait for device authentication
|
||||||
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
|
websocket = new WebSocket(passhint);
|
||||||
if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
|
websocket.onopen = function (e) { QS('waitpushpanelimage')['opacity'] = '1'; }
|
||||||
if (typeof hardwareKeyChallenge.challenge == 'string') { hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), function (c) { return c.charCodeAt(0) }).buffer; }
|
websocket.onmessage = function (e) {
|
||||||
|
if (typeof e.data != 'string') { this.close(); }
|
||||||
publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
|
var r = null;
|
||||||
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
|
try { r = JSON.parse(e.data); } catch (ex) { }
|
||||||
publicKeyCredentialRequestOptions.allowCredentials.push(
|
if (r.sent === true) {
|
||||||
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), function (c) { return c.charCodeAt(0) }), type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] }
|
// Request was sent
|
||||||
);
|
QH('message8', '<span class="msg success"><b>' + format("Request sent, {0}.", r.code) + '</b></span><br /><br />');
|
||||||
|
} else if (r.sent === false) {
|
||||||
|
// Request failed to send
|
||||||
|
QH('message8', '<span class="msg error"><b style=color:#8C001A>' + "Failed to send request." + '<b></span><br /><br />');
|
||||||
|
this.close();
|
||||||
|
} else if (r.approved === true) {
|
||||||
|
// Request approved
|
||||||
|
this.close();
|
||||||
|
QV('tokenInputRememberLabel2', false);
|
||||||
|
QH('message8', '<span class="msg success"><b>' + "Request Accepted." + '</b></span><br /><br />');
|
||||||
|
Q('pushtokenInput').value = r.token;
|
||||||
|
Q('pushOkButton').click();
|
||||||
|
} else {
|
||||||
|
// Request rejected
|
||||||
|
QH('message8', '<span class="msg error"><b style=color:#8C001A>' + "Access Rejected." + '<b></span><br /><br />');
|
||||||
|
this.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// New WebAuthn hardware keys
|
websocket.onclose = function (e) { QS('waitpushpanelimage')['opacity'] = '0.3'; }
|
||||||
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
|
websocket.onerror = function (e) {
|
||||||
function (rawAssertion) {
|
QH('message8', '<span class="msg error"><b style=color:#8C001A>' + "Connection Error" + '<b></span><br /><br />');
|
||||||
var assertion = {
|
QS('waitpushpanelimage')['opacity'] = '0.5';
|
||||||
id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))),
|
|
||||||
clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))),
|
|
||||||
userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))),
|
|
||||||
signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))),
|
|
||||||
authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))),
|
|
||||||
};
|
|
||||||
Q('resetHwtokenInput').value = JSON.stringify(assertion);
|
|
||||||
QE('resetTokenOkButton', true);
|
|
||||||
Q('resetTokenOkButton').click();
|
|
||||||
},
|
|
||||||
function (error) { console.log('credentials-get error', error); }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a hardware security key
|
// Use a hardware security key
|
||||||
|
|
122
webserver.js
122
webserver.js
|
@ -953,17 +953,59 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((req.body.hwtoken == '**push**') && push2fa) {
|
if ((req.body.hwtoken == '**push**') && push2fa) {
|
||||||
// Cause push notification to device
|
const logincodeb64 = Buffer.from(obj.common.zeroPad(getRandomSixDigitInteger(), 6)).toString('base64');
|
||||||
const logincode = obj.common.zeroPad(getRandomSixDigitInteger(), 6);
|
const sessioncode = obj.crypto.randomBytes(24).toString('base64');
|
||||||
const code = Buffer.from(logincode).toString('base64');
|
|
||||||
const authCookie = parent.encodeCookie({ a: 'checkAuth', c: code, u: user._id, n: user.otpdev });
|
// Create a browser cookie so the browser can connect using websocket and wait for device accept/reject.
|
||||||
var payload = { notification: { title: "MeshCentral", body: user.name + " authentication" }, data: { url: '2fa://auth?code=' + code + '&c=' + authCookie } };
|
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; // "Sending notification..." message
|
||||||
|
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
|
var options = { priority: 'High', timeToLive: 60 }; // TTL: 1 minute
|
||||||
parent.firebase.sendToDevice(user.otpdev, payload, options, function (id, err, errdesc) {
|
parent.firebase.sendToDevice(user.otpdev, payload, options, function (id, err, errdesc) {
|
||||||
if (err == null) {
|
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
|
// Request that the login page wait for device auth
|
||||||
req.session.messageid = 5; // "Notification sent." message
|
req.session.messageid = 5; // "Notification sent." message
|
||||||
req.session.passhint = logincode;
|
req.session.passhint = logincode + '|' + url;
|
||||||
req.session.loginmode = '8';
|
req.session.loginmode = '8';
|
||||||
} else {
|
} else {
|
||||||
// Indicate the push notification failed
|
// Indicate the push notification failed
|
||||||
|
@ -972,6 +1014,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
}
|
}
|
||||||
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)); }
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1116,6 +1159,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
// Regenerate session when signing in to prevent fixation
|
// Regenerate session when signing in to prevent fixation
|
||||||
//req.session.regenerate(function () {
|
//req.session.regenerate(function () {
|
||||||
// Store the user's primary key in the session store to be retrieved, or in this case the entire user object
|
// Store the user's primary key in the session store to be retrieved, or in this case the entire user object
|
||||||
|
delete req.session.u2fchallenge;
|
||||||
delete req.session.loginmode;
|
delete req.session.loginmode;
|
||||||
delete req.session.tokenuserid;
|
delete req.session.tokenuserid;
|
||||||
delete req.session.tokenusername;
|
delete req.session.tokenusername;
|
||||||
|
@ -1318,6 +1362,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
// Check everything is ok
|
// Check everything is ok
|
||||||
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (typeof req.body.rpassword1 != 'string') || (typeof req.body.rpassword2 != 'string') || (req.body.rpassword1 != req.body.rpassword2) || (typeof req.body.rpasswordhint != 'string') || (req.session == null) || (typeof req.session.resettokenusername != 'string') || (typeof req.session.resettokenpassword != 'string')) {
|
if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap') || (typeof req.body.rpassword1 != 'string') || (typeof req.body.rpassword2 != 'string') || (req.body.rpassword1 != req.body.rpassword2) || (typeof req.body.rpasswordhint != 'string') || (req.session == null) || (typeof req.session.resettokenusername != 'string') || (typeof req.session.resettokenpassword != 'string')) {
|
||||||
parent.debug('web', 'handleResetPasswordRequest: checks failed');
|
parent.debug('web', 'handleResetPasswordRequest: checks failed');
|
||||||
|
delete req.session.u2fchallenge;
|
||||||
delete req.session.loginmode;
|
delete req.session.loginmode;
|
||||||
delete req.session.tokenuserid;
|
delete req.session.tokenuserid;
|
||||||
delete req.session.tokenusername;
|
delete req.session.tokenusername;
|
||||||
|
@ -1400,6 +1445,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
} else {
|
} else {
|
||||||
// Failed, error out.
|
// Failed, error out.
|
||||||
parent.debug('web', 'handleResetPasswordRequest: failed authenticate()');
|
parent.debug('web', 'handleResetPasswordRequest: failed authenticate()');
|
||||||
|
delete req.session.u2fchallenge;
|
||||||
delete req.session.loginmode;
|
delete req.session.loginmode;
|
||||||
delete req.session.tokenuserid;
|
delete req.session.tokenuserid;
|
||||||
delete req.session.tokenusername;
|
delete req.session.tokenusername;
|
||||||
|
@ -2757,6 +2803,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
}
|
}
|
||||||
handleLoginRequest(req, res, true); break;
|
handleLoginRequest(req, res, true); break;
|
||||||
}
|
}
|
||||||
|
case 'pushlogin': {
|
||||||
|
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
|
||||||
|
handleRootRequestEx(req, res, domain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleLoginRequest(req, res, true); break;
|
||||||
|
}
|
||||||
case 'changepassword': { handlePasswordChangeRequest(req, res, true); break; }
|
case 'changepassword': { handlePasswordChangeRequest(req, res, true); break; }
|
||||||
case 'deleteaccount': { handleDeleteAccountRequest(req, res, true); break; }
|
case 'deleteaccount': { handleDeleteAccountRequest(req, res, true); break; }
|
||||||
case 'createaccount': { handleCreateAccountRequest(req, res, true); break; }
|
case 'createaccount': { handleCreateAccountRequest(req, res, true); break; }
|
||||||
|
@ -4233,6 +4290,58 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
ws.on('close', function (req) { });
|
ws.on('close', function (req) { });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle the 2FA hold web socket
|
||||||
|
// Accept an hold a web socket connection until the 2FA response is received.
|
||||||
|
function handle2faHoldWebSocket(ws, req) {
|
||||||
|
const domain = checkUserIpAddress(ws, req);
|
||||||
|
if (domain == null) { return; }
|
||||||
|
ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
|
||||||
|
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; }
|
||||||
|
|
||||||
|
// 2FA event subscription
|
||||||
|
obj.parent.AddEventDispatch(['2fadev-' + cookie.s], ws);
|
||||||
|
ws.cookie = cookie;
|
||||||
|
ws.HandleEvent = function (source, event, ids, id) {
|
||||||
|
obj.parent.RemoveAllEventDispatch(this);
|
||||||
|
if ((event.approved === true) && (event.userid == this.cookie.u)) {
|
||||||
|
// Create a login cookie
|
||||||
|
const loginCookie = obj.parent.encodeCookie({ a: 'pushAuth', u: event.userid, d: event.domain }, obj.parent.loginCookieEncryptionKey);
|
||||||
|
try { ws.send(JSON.stringify({ approved: true, token: loginCookie })); } catch (ex) { }
|
||||||
|
} else {
|
||||||
|
// Reject the login
|
||||||
|
try { ws.send(JSON.stringify({ approved: false })); } catch (ex) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not accept any data on this connection.
|
||||||
|
ws.on('message', function (data) { this.close(); });
|
||||||
|
|
||||||
|
// If error, do nothing.
|
||||||
|
ws.on('error', function (err) { });
|
||||||
|
|
||||||
|
// If closed, unsubscribe
|
||||||
|
ws.on('close', function (req) { obj.parent.RemoveAllEventDispatch(this); });
|
||||||
|
|
||||||
|
// Perform push notification to device
|
||||||
|
try {
|
||||||
|
const deviceCookie = parent.encodeCookie({ a: 'checkAuth', c: cookie.c, u: cookie.u, n: cookie.n, s: cookie.s });
|
||||||
|
var code = Buffer.from(cookie.c, 'base64').toString();
|
||||||
|
var payload = { notification: { title: (domain.title ? domain.title : 'MeshCentral'), body: "Authentication - " + code }, data: { url: '2fa://auth?code=' + cookie.c + '&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) {
|
||||||
|
try { ws.send(JSON.stringify({ sent: true, code: code })); } catch (ex) { }
|
||||||
|
} else {
|
||||||
|
try { ws.send(JSON.stringify({ sent: false })); } catch (ex) { }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ex) { console.log(ex); }
|
||||||
|
}
|
||||||
|
|
||||||
// Get the total size of all files in a folder and all sub-folders. (TODO: try to make all async version)
|
// Get the total size of all files in a folder and all sub-folders. (TODO: try to make all async version)
|
||||||
function readTotalFileSize(path) {
|
function readTotalFileSize(path) {
|
||||||
var r = 0, dir;
|
var r = 0, dir;
|
||||||
|
@ -5336,6 +5445,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||||
obj.app.post(url + 'oneclickrecovery.ashx', handleOneClickRecoveryFile);
|
obj.app.post(url + 'oneclickrecovery.ashx', handleOneClickRecoveryFile);
|
||||||
obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
|
obj.app.get(url + 'userfiles/*', handleDownloadUserFiles);
|
||||||
obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
|
obj.app.ws(url + 'echo.ashx', handleEchoWebSocket);
|
||||||
|
obj.app.ws(url + '2fahold.ashx', handle2faHoldWebSocket);
|
||||||
obj.app.ws(url + 'apf.ashx', function (ws, req) { obj.parent.mpsserver.onWebSocketConnection(ws, req); })
|
obj.app.ws(url + 'apf.ashx', function (ws, req) { obj.parent.mpsserver.onWebSocketConnection(ws, req); })
|
||||||
obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
|
obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
|
||||||
obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking.
|
obj.app.get(url + 'health.ashx', function (req, res) { res.send('ok'); }); // TODO: Perform more server checking.
|
||||||
|
|
Loading…
Reference in a new issue