This commit is contained in:
Bryan Roe 2019-07-22 17:23:34 -07:00
commit f9d2b551c6
6 changed files with 128 additions and 13 deletions

View File

@ -39,9 +39,12 @@ module.exports.CreateMeshMail = function (parent) {
// Set default mail templates
// You can override these by placing a file with the same name in "meshcentral-data/mail"
// If the server hash many domains, just add the domainid to the file like this: 'account-check-customer1.html', 'mesh-invite-customer1.txt'.
obj.mailTemplates['account-invite.html'] = '<title>[[[SERVERNAME]]] - Account Invitation</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Account Invitation</b></td></tr></table><p>An account was created for you on server <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a>, you can access it now with:</p><p>&nbsp;&nbsp;&nbsp;Username: <b>[[[ACCOUNTNAME]]]</b><br />&nbsp;&nbsp;&nbsp;Password: <b>[[[PASSWORD]]]</b></p>Best regards,<br>[[[USERNAME]]]<br></div>';
obj.mailTemplates['account-check.html'] = '<title>[[[SERVERNAME]]] - Email Verification</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Verification</b></td></tr></table><p>Hi [[[USERNAME]]], <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a> is requesting email verification, click on the following link to complete the process.</p><p style="margin-left:30px"><a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]">Click here to verify your e-mail address.</a></p>If you did not initiate this request, please ignore this mail.</div>';
obj.mailTemplates['account-reset.html'] = '<title>[[[SERVERNAME]]] - Account Reset</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Verification</b></td></tr></table><p>Hi [[[USERNAME]]], <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a> is requesting an account password reset, click on the following link to complete the process.</p><p style="margin-left:30px"><a href="[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]">Click here to reset your account password.</a></p>If you did not initiate this request, please ignore this mail.</div>';
obj.mailTemplates['mesh-invite.html'] = '<title>[[[SERVERNAME]]] - Invitation</title>\r\n<div style="font-family:Arial,Helvetica,sans-serif"><table style="background-color:#003366;color:lightgray;width:100%" cellpadding=8><tr><td><b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Agent Installation</b></td></tr></table>[[[AREA-NAME]]]<p>Hello [[[NAME]]],</p>[[[/AREA-NAME]]]<p>User [[[USERNAME]]] on server <a href="[[[SERVERURL]]]">[[[SERVERNAME]]]</a> is requesting you to install software to start a remote control session.</p>[[[AREA-MSG]]]<p>Message: <b>[[[MSG]]]</b></p>[[[/AREA-MSG]]][[[AREA-WINDOWS]]]<p style="margin-left:30px"><a href="[[[SERVERURL]]]/meshagents?id=3&meshid=[[[MESHIDHEX]]]&tag=mailto:[[[EMAIL]]]&installflags=[[[INSTALLFLAGS]]]">Click here to download the MeshAgent for Windows.</a></p>[[[/AREA-WINDOWS]]][[[AREA-OSX]]]<p style="margin-left:30px"><a href="[[[SERVERURL]]]/meshagents?id=16&meshid=[[[MESHIDHEX]]]&tag=mailto:[[[EMAIL]]]&installflags=[[[INSTALLFLAGS]]]">Click here to download the MeshAgent for Apple OSX.</a></p>[[[/AREA-OSX]]][[[AREA-LINUX]]]<p>For Linux, cut & paste the following in a terminal to install the agent:<br /><pre style="margin-left:30px">wget -q [[[SERVERURL]]]/meshagents?script=1 --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh [[[SERVERURL]]] \'[[[MESHIDHEX]]]\'</pre></p>[[[/AREA-LINUX]]][[[AREA-LINK]]]<p>To install the software, <a href="[[[SERVERURL]]][[[LINKURL]]]">click here</a> and follow the instructions.</p>[[[/AREA-LINK]]]<p>If you did not initiate this request, please ignore this mail.</p>Best regards,<br>[[[USERNAME]]]<br></div>';
obj.mailTemplates['account-invite.txt'] = '[[[SERVERNAME]]] - Account Invitation\r\nAn account was created for you on server [[[SERVERNAME]]] ([[[SERVERURL]]]/), you can access it now with username \"[[[ACCOUNTNAME]]]\" and password \"[[[PASSWORD]]]\".\r\n\r\nBest regards,\r\n[[[USERNAME]]]';
obj.mailTemplates['account-check.txt'] = '[[[SERVERNAME]]] - Email Verification\r\nHi [[[USERNAME]]], [[[SERVERNAME]]] ([[[SERVERURL]]]) is performing an e-mail verification. Nagivate to the following link to complete the process:\r\n\r\n[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]\r\n\r\nIf you did not initiate this request, please ignore this mail.\r\n';
obj.mailTemplates['account-reset.txt'] = '[[[SERVERNAME]]] - Account Reset\r\nHi [[[USERNAME]]], [[[SERVERNAME]]] ([[[SERVERURL]]]) is requesting an account password reset. Nagivate to the following link to complete the process:\r\n\r\n[[[SERVERURL]]]/checkmail?c=[[[COOKIE]]]\r\n\r\nIf you did not initiate this request, please ignore this mail.';
obj.mailTemplates['mesh-invite.txt'] = '[[[SERVERNAME]]] - Invitation\r\n[[[AREA-NAME]]]Hello [[[NAME]]],\r\n\r\n[[[/AREA-NAME]]]User [[[USERNAME]]] on server [[[SERVERNAME]]] ([[[SERVERURL]]]/) is requesting you install software to start the remote control session.[[[AREA-MSG]]]\r\n\r\nMessage: [[[MSG]]]\r\n\r\n[[[/AREA-MSG]]][[[AREA-WINDOWS]]]For Windows, nagivate to the following link to complete the process:\r\n\r\n[[[SERVERURL]]]/meshagents?id=3&meshid=[[[MESHIDHEX]]]&tag=mailto:[[[EMAIL]]]&installflags=[[[INSTALLFLAGS]]]\r\n\r\n[[[/AREA-WINDOWS]]][[[AREA-OSX]]]For Apple OSX, nagivate to the following link to complete the process:\r\n\r\n[[[SERVERURL]]]/meshagents?id=16&meshid=[[[MESHIDHEX]]]&tag=mailto:[[[EMAIL]]]&installflags=[[[INSTALLFLAGS]]]\r\n\r\n[[[/AREA-OSX]]][[[AREA-LINUX]]]For Linux, cut & paste the following in a terminal to install the agent:\r\n\r\nwget -q [[[SERVERURL]]]/meshagents?script=1 --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh [[[SERVERURL]]] \'[[[MESHIDHEX]]]\'\r\n\r\n[[[/AREA-LINUX]]][[[AREA-LINK]]]To install the software, navigate to [[[SERVERURL]]][[[LINKURL]]] and follow the instructions.\r\n\r\n[[[/AREA-LINK]]]If you did not initiate this request, please ignore this mail.\r\n\r\nBest regards,\r\n[[[USERNAME]]]';
@ -126,6 +129,19 @@ module.exports.CreateMeshMail = function (parent) {
sendNextMail();
};
// Send account invitation mail
obj.sendAccountInviteMail = function (domain, username, accountname, email, password) {
var template = getTemplateEx('account-invite', domain);
if ((template == null) || (template.htmlSubject == null) || (template.txtSubject == null) || (parent.certificates == null) || (parent.certificates.CommonName == null) || (parent.certificates.CommonName.indexOf('.') == -1)) return; // If the server name is not set, invitation not possible.
// Set all the options.
var options = { username: username, accountname: accountname, email: email, servername: domain.title, password: password };
// Send the email
obj.pendingMails.push({ to: email, from: parent.config.smtp.from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
sendNextMail();
};
// Send account check mail
obj.sendAccountCheckMail = function (domain, username, email) {
var template = getTemplateEx('account-check', domain);

View File

@ -1139,6 +1139,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// If the email is the username, set this here.
if (domain.usernameisemail) { if (command.email) { command.username = command.email; } else { command.email = command.username; } }
// Randomize the password if needed
if (command.randomPassword === true) { command.pass = getRandomPassword(); }
// Add a new user account
var err = null, newusername, newuserid;
try {
@ -1147,7 +1150,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
else if (common.validateUsername(command.username, 1, 256) == false) { err = 'Invalid username'; } // Username is between 1 and 64 characters, no spaces
else if (common.validateString(command.pass, 1, 256) == false) { err = 'Invalid password'; } // Password is between 1 and 256 characters
else if (command.username.indexOf('/') >= 0) { err = 'Invalid username'; } // Usernames can't have '/'
else if (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false) { err = 'Invalid password'; } // Password does not meet requirements
else if ((command.randomPassword !== true) && (common.checkPasswordRequirements(command.pass, domain.passwordrequirements) == false)) { err = 'Invalid password'; } // Password does not meet requirements
else if ((command.email != null) && (common.validateEmail(command.email, 1, 1024) == false)) { err = 'Invalid email'; } // Check if this is a valid email address
else {
newusername = command.username;
@ -1210,6 +1213,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to create the user. Another event will come.
parent.parent.DispatchEvent(targets, obj, event);
// Perform email invitation
if ((command.emailInvitation == true) && (command.emailVerified == true) && command.email && parent.parent.mailserver) {
parent.parent.mailserver.sendAccountInviteMail(domain, user.name, newusername, command.email.toLowerCase(), command.pass);
}
// OK Response
if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'adduser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } }
} else {
@ -1291,6 +1299,39 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}
}
}
break;
}
case 'changemeshnotify':
{
var err = null;
try {
// Change the current user's notification flags for a meshid
if (common.validateString(command.meshid, 1, 1024) == false) { err = 'Invalid group identifier'; } // Check the meshid
else if (command.meshid.indexOf('/') == -1) { command.meshid = 'mesh/' + domain.id + '/' + command.meshid; }
if (common.validateInt(command.notify) == false) { err = 'Invalid notification flags'; }
if ((user.links == null) || (user.links[command.meshid] == null)) { err = 'Incorrect group identifier'; }
} catch (ex) { err = 'Validation exception: ' + ex; }
// Handle any errors
if (err != null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'changemeshnotify', responseid: command.responseid, result: err })); } catch (ex) { } } break; }
// Change the notification
if (command.notify == 0) {
delete user.links[command.meshid].notify;
} else {
user.links[command.meshid].notify = command.notify;
}
// Save the user
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', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Mesh notification change.', 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 'changepassword':
@ -2824,6 +2865,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Clean a IPv6 address that encodes a IPv4 address
function cleanRemoteAddr(addr) { if (addr.startsWith('::ffff:')) { return addr.substring(7); } else { return addr; } }
function getRandomPassword() { return Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); }
return obj;
};

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.3.8-f",
"version": "0.3.8-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

@ -1282,6 +1282,7 @@
if (updateNaggleFlags & 256) { drawDeviceTimeline(); }
if (updateNaggleFlags & 1024) { deviceEventsUpdate(); }
if (updateNaggleFlags & 2048) { userEventsUpdate(); }
if (updateNaggleFlags & 4096) { p20updateMesh(); }
updateNaggleTimer = null;
updateNaggleFlags = 0;
}, 150);
@ -1295,7 +1296,7 @@
QV('authAppSetupCheck', userinfo.otpsecret == 1);
QV('authKeySetupCheck', userinfo.otphkeys > 0);
QV('authCodesSetupCheck', userinfo.otpkeys > 0);
masterUpdate(4 + 128);
masterUpdate(4 + 128 + 4096);
// If we can't create new groups, hide all links that can do that.
var newGroupsAllowed = ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 64) == 0));
@ -1791,7 +1792,7 @@
//meshserver.send({ action: 'files' }); // TODO: Why do we need to do this??
// If we are looking at a mesh that is now deleted, move back to "My Account"
if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { p20updateMesh(); }
if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { masterUpdate(4096); }
break;
}
case 'deletemesh': {
@ -1946,7 +1947,14 @@
var node = nodes[index];
// Event the connection change if needed
var n = getstore('notifications', 0);
var n = getstore('notifications', 0); // Account notification settings
// Per-group notification settings
if (message.event.meshid && userinfo.links && userinfo.links[message.event.meshid] && userinfo.links[message.event.meshid].notify) {
n |= userinfo.links[message.event.meshid].notify;
}
// Show the notification
if (n & 2) {
if (((node.conn & 1) == 0) && ((message.event.conn & 1) != 0)) { addNotification({ text: 'Agent connected', title: node.name, icon: node.icon, nodeid: node._id }); }
if (((node.conn & 2) == 0) && ((message.event.conn & 2) != 0)) { addNotification({ text: 'Intel AMT detected', title: node.name, icon: node.icon, nodeid: node._id }); }
@ -6379,6 +6387,15 @@
x += addHtmlValue('User Consent', addLinkConditional(meshFeatures, 'p20editmeshconsent()', meshrights & 1));
}
// Display user consent
var meshNotify = 0, meshNotifyStr = [];
if (userinfo.links && userinfo.links[currentMesh._id] && userinfo.links[currentMesh._id].notify) { meshNotify = userinfo.links[currentMesh._id].notify; }
if (meshNotify & 2) { meshNotifyStr.push('Connect'); }
if (meshNotify & 4) { meshNotifyStr.push('Disconnect'); }
if (meshNotify & 8) { meshNotifyStr.push('Intel&reg; AMT'); }
if (meshNotifyStr.length == 0) { meshNotifyStr.push('None'); }
x += addHtmlValue('Notifications', addLink(meshNotifyStr.join(', '), 'p20editMeshNotify()'));
// Intel AMT setup
var intelAmtPolicy = 'No Policy';
if (currentMesh.amt) {
@ -6755,6 +6772,29 @@
function p20deleteUser(e, userid) { haltEvent(e); p20viewuserEx(2, decodeURIComponent(userid)); return false; }
function p20viewuserEx2(button, userid) { meshserver.send({ action: 'removemeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: userid }); }
function p20editMeshNotify() {
if (xxdialogMode) return false;
var meshNotify = 0;
if (userinfo.links && userinfo.links[currentMesh._id] && userinfo.links[currentMesh._id].notify) { meshNotify = userinfo.links[currentMesh._id].notify; }
var x = '';
x += '<div><input id=p20notifyIntelDeviceConnect type=checkbox />Device connections.</div>';
x += '<div><input id=p20notifyIntelDeviceDisconnect type=checkbox />Device disconnections.</div>';
x += '<div><input id=p20notifyIntelAmtKvmActions type=checkbox />Intel&reg; AMT desktop and serial events.</div>';
setDialogMode(2, "Notification Settings", 3, p20editMeshNotifyEx, x);
Q('p20notifyIntelDeviceConnect').checked = (meshNotify & 2);
Q('p20notifyIntelDeviceDisconnect').checked = (meshNotify & 4);
Q('p20notifyIntelAmtKvmActions').checked = (meshNotify & 8);
return false;
}
function p20editMeshNotifyEx() {
var meshNotify = 0;
meshNotify += Q('p20notifyIntelDeviceConnect').checked ? 2 : 0;
meshNotify += Q('p20notifyIntelDeviceDisconnect').checked ? 4 : 0;
meshNotify += Q('p20notifyIntelAmtKvmActions').checked ? 8 : 0;
meshserver.send({ action: 'changemeshnotify', meshid: currentMesh._id, notify: meshNotify });
}
//
// MY FILES
//
@ -7325,8 +7365,12 @@
x += addHtmlValue('Email', '<input id=p4email maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
x += addHtmlValue('Password', '<input id=p4pass1 type=password maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
x += addHtmlValue('Password', '<input id=p4pass2 type=password maxlength=256 onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
x += '<div><input id=p4resetNextLogin type=checkbox />Force password reset on next login.</div>';
if (serverinfo.emailcheck) { x += '<div><input id=p4verifiedEmail type=checkbox />Email is verified.</div>'; }
x += '<div><input id=p4randomPassword onchange=showCreateNewAccountDialogValidate() type=checkbox />Randomize the password.</div>';
x += '<div><input id=p4resetNextLogin onchange=showCreateNewAccountDialogValidate() type=checkbox />Force password reset on next login.</div>';
if (serverinfo.emailcheck) {
x += '<div><input id=p4verifiedEmail onchange=showCreateNewAccountDialogValidate() type=checkbox />Email is verified.</div>';
x += '<div><input id=p4invitationEmail type=checkbox />Send invitation email.</div>';
}
if (passRequirements) {
var r = [], rc = 0;
@ -7340,18 +7384,31 @@
}
function showCreateNewAccountDialogValidate(x) {
if ((x == null) && (Q('p4email').value.length > 0) && (validateEmail(Q('p4email').value)) == false) { QE('idx_dlgOkButton', false); return; }
var ve = validateEmail(Q('p4email').value);
if (serverinfo.emailcheck) {
QE('p4verifiedEmail', ve);
QE('p4invitationEmail', ve && Q('p4resetNextLogin').checked && Q('p4verifiedEmail').checked);
if (ve == false) { Q('p4verifiedEmail').checked = false; }
if ((Q('p4resetNextLogin').checked == false) || (Q('p4verifiedEmail').checked == false)) { Q('p4invitationEmail').checked = false; }
}
QE('p4pass1', !Q('p4randomPassword').checked);
QE('p4pass2', !Q('p4randomPassword').checked);
if ((x == null) && (Q('p4email').value.length > 0) && (ve == false)) { QE('idx_dlgOkButton', false); return; }
var ok = true;
if ((features & 0x200000) == 0) { ok &= (!Q('p4name') || ((Q('p4name').value.length > 0) && (Q('p4name').value.indexOf(' ') == -1))); }
ok &= (Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value && checkPasswordRequirements(Q('p4pass1').value, passRequirements));
if (Q('p4randomPassword').checked == false) { ok &= (Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value && checkPasswordRequirements(Q('p4pass1').value, passRequirements)); }
if (ok && passRequirements) { if (checkPasswordRequirements(Q('p4pass1').value, passRequirements) == false) { ok = false; } }
QE('idx_dlgOkButton', ok);
}
function showCreateNewAccountDialogEx() {
var username = ((features & 0x200000) == 0) ? Q('p4name').value : Q('p4email').value;
var x = { action: 'adduser', username: username, email: Q('p4email').value, pass: Q('p4pass1').value, resetNextLogin: Q('p4resetNextLogin').checked };
if (serverinfo.emailcheck) { x.emailVerified = Q('p4verifiedEmail').checked; }
var x = { action: 'adduser', username: username, email: Q('p4email').value, pass: Q('p4pass1').value, resetNextLogin: Q('p4resetNextLogin').checked, randomPassword: Q('p4randomPassword').checked };
if (serverinfo.emailcheck) {
x.emailVerified = Q('p4verifiedEmail').checked;
x.emailInvitation = Q('p4invitationEmail').checked;
}
meshserver.send(x);
}