Improved user handling with domain auth, email validation.

This commit is contained in:
Ylian Saint-Hilaire 2019-01-16 12:04:48 -08:00
parent ff173b8788
commit 3b2e78cddf
12 changed files with 128 additions and 48 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -199,7 +199,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var httpport = ((obj.args.aliasport != null) ? obj.args.aliasport : obj.args.port);
// Build server information object
var serverinfo = { name: obj.parent.certificates.CommonName, mpsname: obj.parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: obj.args.mpspass, port: httpport, emailcheck: obj.parent.parent.mailserver != null };
var serverinfo = { name: obj.parent.certificates.CommonName, mpsname: obj.parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: obj.args.mpspass, port: httpport, emailcheck: ((obj.parent.parent.mailserver != null) && (domain.auth != 'sspi')), domainauth: (domain.auth == 'sspi') };
if (obj.args.notls == true) { serverinfo.https = false; } else { serverinfo.https = true; serverinfo.redirport = obj.args.redirport; }
// Send server information
@ -470,6 +470,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
case 'changeemail':
{
// Change the email address
if (domain.auth == 'sspi') return;
if (obj.common.validateEmail(command.email, 1, 256) == false) return;
if (obj.parent.users[req.session.userid].email != command.email) {
// Check if this email is already validated on a different account
@ -501,6 +502,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
message.msg = 'Set email of user ' + userinfo.name + ' to ' + user.email;
}
obj.parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, message);
// Send the verification email
if ((obj.parent.parent.mailserver != null) && (domain.auth != 'sspi')) { obj.parent.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
}
});
}
@ -509,16 +513,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
case 'verifyemail':
{
// Send a account email verification email
if (domain.auth == 'sspi') return;
if (obj.common.validateString(command.email, 3, 1024) == false) return;
var x = command.email.split('@');
if ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2)) {
if (obj.parent.users[req.session.userid].email == command.email) {
if ((obj.parent.parent.mailserver != null) && (obj.parent.users[req.session.userid].email == command.email)) {
// Send the verification email
if (obj.parent.parent.mailserver != null) {
obj.parent.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email);
}
}
}
break;
}
case 'wssessioncount':
@ -738,6 +738,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
case 'createmesh':
{
// In some situations, we need a verified email address to create a device group.
if ((obj.parent.parent.mailserver != null) && (domain.auth != 'sspi') && (user.emailVerified !== true) && (user.siteadmin != 0xFFFFFFFF)) return; // User must verify it's email first.
// Create mesh
if (obj.common.validateString(command.meshname, 1, 64) == false) break; // Meshname is between 1 and 64 characters
if (obj.common.validateString(command.desc, 0, 1024) == false) break; // Mesh description is between 0 and 1024 characters

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.2.6-h",
"version": "0.2.6-i",
"keywords": [
"Remote Management",
"Intel AMT",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -237,12 +237,16 @@
<p><strong>Account actions</strong></p>
<div style="margin-left:9px;margin-bottom:8px">
<div style="margin-top:5px"><span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a></span></div>
<div style="margin-top:5px">
<span id="otpAuth" style="display:none"><a onclick="account_addOtp()" style="cursor:pointer">Add 2-step login</a><br /></span>
<span id="otpAuthRemove" style="display:none"><a onclick="account_removeOtp()" style="cursor:pointer">Remove 2-step login</a><br /></span>
</div>
<div style="margin-top:5px"><a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a></div>
<div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a></div>
<div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a></div>
</div>
</div>
<br style=clear:both />
</div>
<strong>Device Groups</strong>
( <a onclick=account_createMesh() style=cursor:pointer><img height=12 src="images/icon-addnew.png" width=12 border=0 /> New</a> )
<br /><br />
@ -560,7 +564,7 @@
<div id=topMenu style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:0px 0px 5px 5px;position:fixed;top:50px;right:5px;width:170px;display:none">
<div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(2)>My Files</div>
<div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(1)>My Account</div>
<a href=/logout><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Logout</div></a>
<div id="logoutMenuOption"><a href=/logout><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Logout</div></a></div>
</div>
<iframe name="fileUploadFrame" style=display:none></iframe>
<script>
@ -647,14 +651,17 @@
switch (message.action) {
case 'serverinfo': {
serverinfo = message.serverinfo;
QV('p3AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide Account Actions if in single user mode or domain authentication
QV('logoutMenuOption', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide logout if in single user mode or domain authentication
break;
}
case 'userinfo': {
userinfo = message.userinfo;
QH('p3userName', userinfo.name);
//updateSiteAdmin();
//QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
//QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
QV('otpAuth', ((features & 4096) != 0) && (userinfo.otpsecret != 1));
QV('otpAuthRemove', ((features & 4096) != 0) && (userinfo.otpsecret == 1));
break;
}
case 'users': {
@ -713,6 +720,28 @@
if (currentNode._id == message.nodeid) { drawDeviceTimeline(); }
break;
}
case 'otpauth-request': {
if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
var secret = message.secret;
if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
QH('d2optinfo', 'Install <a href=\"https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2\" rel=\"noreferrer noopener\" target=_blank>Google Authenticator</a> or a compatible application, use <a href=\"' + message.url + '\" rel=\"noreferrer noopener\" target=_blank> this link</a> or enter the secret below. Then, enter the current 6 digit token to activate 2-Step login.<br /><br /><div style=width:100%;text-align:center><tt id=d2optsecret secret=\"' + message.secret + '\" style=font-size:15px>' + secret + '</tt><br /><br />Token: <input type=text onkeypress=\"return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)\" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></div>');
QV('idx_dlgOkButton', true);
QE('idx_dlgOkButton', false);
Q('d2otpauthinput').focus();
}
break;
}
case 'otpauth-setup': {
if (xxdialogMode) return;
setDialogMode(2, "Add 2-Step Login", 1, null, message.success ? "<b style=color:green>2-step login activation successful</b>. You will now need a valid token to login again." : "<b style=color:red>2-step login activation failed</b>. Clear the secret from the application and try again. You only have a few minutes to enter the proper code.");
break;
}
case 'otpauth-clear': {
if (xxdialogMode) return;
setDialogMode(2, "Remove 2-Step Login", 1, null, message.success ? "<b style=color:green>2-step login activation removed</b>. You can reactivate this feature at any time." : "<b style=color:red>2-step login activation removal failed</b>. Try again.");
break;
}
case 'event': {
/*
if (!message.event.nolog) {
@ -723,6 +752,18 @@
}
*/
switch (message.event.action) {
case 'accountchange': {
// An account was created or changed
if (userinfo.name == message.event.account.name) {
var newsiteadmin = message.event.account.siteadmin ? message.event.account.siteadmin : 0;
var oldsiteadmin = userinfo.siteadmin ? userinfo.siteadmin : 0;
if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); }
userinfo = message.event.account;
if (oldsiteadmin != newsiteadmin) updateSiteAdmin();
QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
}
break;
}
case 'createmesh': {
// A new mesh was created
if (message.event.links['user/' + domain + '/' + userinfo.name.toLowerCase()] != null) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
@ -952,6 +993,23 @@
// MY ACCOUNT
//
function account_addOtp() {
if (xxdialogMode || (userinfo.otpsecret == 1) || ((features & 4096) == 0)) return;
setDialogMode(2, "Add 2-Step Login", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').attributes.secret.value, token: Q('d2otpauthinput').value }); }, "<div id=d2optinfo>Loading...</div>", 'otpauth-request');
meshserver.send({ action: 'otpauth-request' });
}
function account_addOtpCheck(e) {
var tokenIsValid = (Q('d2otpauthinput').value.length == 6);
QE('idx_dlgOkButton', tokenIsValid);
if (e && (e.keyCode == 13) && tokenIsValid) { dialogclose(1); }
}
function account_removeOtp() {
if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return;
setDialogMode(2, "Remove 2-Step Login", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of 2-step login?");
}
function account_showVerifyEmail() {
if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return;
var x = "Click ok to send a verification mail to:<br /><div style=padding:8px><b>" + EscapeHtml(userinfo.email) + "</b></div>Please wait a few minute to receive the verification.";
@ -973,7 +1031,7 @@
function account_validateEmail(e, email) {
QE('idx_dlgOkButton', validateEmail(Q('dp3email').value) && (Q('dp3email').value != userinfo.email));
if ((x == true) && (e != null) && (e.keyCode == 13)) { dialogclose(1); }
if ((e != null) && (e.keyCode == 13)) { dialogclose(1); }
}
function account_changeEmail() {
@ -1011,6 +1069,11 @@
function account_createMesh() {
if (xxdialogMode) return;
// Check if we are allowed to create a new device group
if ((userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "New Device Group", 1, null, "Unable to create a new device group until the email address is verified. Go to the \"My Account\" menu option to change and verify an email address."); return; }
// We are allowed, let's prompt to information
var x = addHtmlValue('Name', '<input id=dp3meshname style=width:170px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
x += addHtmlValue('Type', '<div style=width:170px;margin:0;padding:0><select id=dp3meshtype style=width:100% onchange=account_validateMeshCreate() ><option value=2>Software Agent Group</option><option value=1>Intel&reg; AMT only</option></select></div>');
x += addHtmlValue('Description', '<div style=width:170px;margin:0;padding:0><textarea id=dp3meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');

View File

@ -246,8 +246,8 @@
<a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><br />
<a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a><br />
</p>
</div>
<br style=clear:both />
</div>
<strong>Device Groups</strong>
( <a onclick=account_createMesh() style=cursor:pointer><img height=12 src="images/icon-addnew.png" width=12 border=0 /> New</a> )
<br /><br />
@ -1074,7 +1074,7 @@
if (noServerBackup == 1) { siteRights &= 0xFFFFFFFA; } // If not server backups allowed, remove server backup and restore permissions
// Update account actions
QV('p2AccountActions', (features & 4) == 0); // Hide Account Actions if in single user mode
QV('p2AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide Account Actions if in single user mode or domain authentication
QV('p2ServerActions', siteRights & 21);
QV('LeftMenuMyServer', siteRights & 21);
QV('MainMenuMyServer', siteRights & 21);
@ -1297,7 +1297,7 @@
}
break;
}
case 'getNotes':{
case 'getNotes': {
var n = Q('d2devNotes');
if (n && (message.id == decodeURIComponent(n.attributes['noteid'].value))) {
if (message.notes) { QH('d2devNotes', decodeURIComponent(message.notes)); } else { QH('d2devNotes', ''); }
@ -5062,9 +5062,9 @@
}
function account_addOtpCheck(e) {
const v = (Q('d2otpauthinput').value.length == 6);
QE('idx_dlgOkButton', v);
if (e && (e.keyCode == 13) && v) { dialogclose(1); }
var tokenIsValid = (Q('d2otpauthinput').value.length == 6);
QE('idx_dlgOkButton', tokenIsValid);
if (e && (e.keyCode == 13) && tokenIsValid) { dialogclose(1); }
}
function account_removeOtp() {
@ -5134,6 +5134,11 @@
function account_createMesh() {
if (xxdialogMode) return;
// Check if we are allowed to create a new device group
if ((userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "New Device Group", 1, null, "Unable to create a new device group until the email address is verified. Go to the \"My Account\" tab to change and verify an email address."); return; }
// We are allowed, let's prompt to information
var x = "Create a new device group using the options below.<br /><br />";
x += addHtmlValue('Name', '<input id=dp2meshname style=width:230px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
x += addHtmlValue('Type', '<div style=width:230px;margin:0;padding:0><select id=dp2meshtype style=width:100% onchange=account_validateMeshCreate() ><option value=2>Manage using a software agent</option><option value=1>Intel&reg; AMT only, no agent</option></select></div>');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -333,7 +333,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleLogoutRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) return;
if ((domain == null) || (domain.auth == 'sspi')) return;
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
// Destroy the user's session to log them out will be re-created next request
if (req.session.userid) {
@ -425,7 +426,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleCreateAccountRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) return;
if ((domain == null) || (domain.auth == 'sspi')) return;
if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; }
if (!obj.common.validateUsername(req.body.username, 1, 64) || !obj.common.validateEmail(req.body.email, 1, 256) || !obj.common.validateString(req.body.password1, 1, 256) || !obj.common.validateString(req.body.password2, 1, 256) || (req.body.password1 != req.body.password2) || req.body.username == '~' || !obj.common.checkPasswordRequirements(req.body.password1, domain.passwordrequirements)) {
req.session.loginmode = 2;
@ -466,7 +468,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
user.salt = salt;
user.hash = hash;
obj.db.SetUser(user);
if (obj.parent.mailserver != null) { obj.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
// Send the verification email
if ((obj.parent.mailserver != null) && (domain.auth != 'sspi') && (obj.common.validateEmail(user.email, 1, 256) == true)) { obj.parent.mailserver.sendAccountCheckMail(domain, user.name, user.email); }
});
obj.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', username: user.name, account: user, action: 'accountcreate', msg: 'Account created, email is ' + req.body.email, domain: domain.id });
}
@ -479,7 +484,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process an account reset request
function handleResetAccountRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) return;
if ((domain == null) || (domain.auth == 'sspi')) return;
if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; }
if (!req.body.email || checkEmail(req.body.email) == false) {
req.session.loginmode = 3;
@ -511,7 +517,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Called to process a web based email verification request
function handleCheckMailRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) return;
if ((domain == null) || (domain.auth == 'sspi')) return;
if (req.query.c != null) {
var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.mailserver.mailCookieEncryptionKey, 30);
if ((cookie != null) && (cookie.u != null) && (cookie.e != null)) {
@ -614,7 +621,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function handleDeleteAccountRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) return;
if ((domain == null) || (domain.auth == 'sspi')) return;
// Check if the user is logged and we have all required parameters
if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url); return; }
var user = obj.users[req.session.userid];
@ -658,7 +666,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Handle password changes
function handlePasswordChangeRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) return;
if ((domain == null) || (domain.auth == 'sspi')) return;
// Check if the user is logged and we have all required parameters
if (!req.session || !req.session.userid || !req.body.apassword1 || (req.body.apassword1 != req.body.apassword2) || (req.session.domainid != domain.id)) { res.redirect(domain.url); return; }
@ -795,19 +804,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Give the web page a list of supported server features
features = 0;
if (obj.args.wanonly == true) { features += 1; } // WAN-only mode
if (obj.args.lanonly == true) { features += 2; } // LAN-only mode
if (obj.args.nousers == true) { features += 4; } // Single user mode
if (domain.userQuota == -1) { features += 8; } // No server files mode
if (obj.args.mpstlsoffload) { features += 16; } // No mutual-auth CIRA
if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 32; } // Allow site within iframe
if ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName != 'un-configured') && (obj.args.lanonly != true)) { features += 64; } // Email invites
if (obj.args.webrtc == true) { features += 128; } // Enable WebRTC (Default false for now)
if (obj.args.clickonce !== false) { features += 256; } // Enable ClickOnce (Default true)
if (obj.args.allowhighqualitydesktop == true) { features += 512; } // Enable AllowHighQualityDesktop (Default false)
if (obj.args.lanonly == true || obj.args.mpsport == 0) { features += 1024; } // No CIRA
if ((obj.parent.serverSelfWriteAllowed == true) && (user != null) && (user.siteadmin == 0xFFFFFFFF)) { features += 2048; } // Server can self-write (Allows self-update)
if ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName != 'un-configured') && (obj.args.lanonly !== true) && (obj.args.nousers !== true)) { features += 4096; } // 2-step login supported
if (obj.args.wanonly == true) { features += 0x0001; } // WAN-only mode
if (obj.args.lanonly == true) { features += 0x0002; } // LAN-only mode
if (obj.args.nousers == true) { features += 0x0004; } // Single user mode
if (domain.userQuota == -1) { features += 0x0008; } // No server files mode
if (obj.args.mpstlsoffload) { features += 0x0010; } // No mutual-auth CIRA
if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 0x0020; } // Allow site within iframe
if ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName != 'un-configured') && (obj.args.lanonly != true)) { features += 0x0040; } // Email invites
if (obj.args.webrtc == true) { features += 0x0080; } // Enable WebRTC (Default false for now)
if (obj.args.clickonce !== false) { features += 0x0100; } // Enable ClickOnce (Default true)
if (obj.args.allowhighqualitydesktop == true) { features += 0x0200; } // Enable AllowHighQualityDesktop (Default false)
if (obj.args.lanonly == true || obj.args.mpsport == 0) { features += 0x0400; } // No CIRA
if ((obj.parent.serverSelfWriteAllowed == true) && (user != null) && (user.siteadmin == 0xFFFFFFFF)) { features += 0x0800; } // Server can self-write (Allows self-update)
if ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName != 'un-configured') && (obj.args.lanonly !== true) && (obj.args.nousers !== true)) { features += 0x1000; } // 2-step login supported
// Send the master web application
if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' <a href=' + domain.url + 'logout?' + Math.random() + ' style=color:white>Logout</a>'; } // If a default user is in use or no user mode, don't display the logout button