From 7292fa116b95f8fabef0fb0d89078eba51d1a342 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 22 Jul 2019 11:25:18 -0700 Subject: [PATCH 1/2] Added per-device group notifications. --- meshuser.js | 33 +++++++++++++++++++++ package.json | 2 +- views/default-min.handlebars | 2 +- views/default-mobile-min.handlebars | 2 +- views/default.handlebars | 46 +++++++++++++++++++++++++++-- 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/meshuser.js b/meshuser.js index 3fcb5ed2..e26b071a 100644 --- a/meshuser.js +++ b/meshuser.js @@ -1291,6 +1291,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': diff --git a/package.json b/package.json index ce32ebd8..24fbcb18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.3.8-f", + "version": "0.3.8-h", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 2631e27c..64666d70 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default-mobile-min.handlebars b/views/default-mobile-min.handlebars index ffb60084..bf953002 100644 --- a/views/default-mobile-min.handlebars +++ b/views/default-mobile-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}
\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}
\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index c0f8ffcb..99994c7c 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -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® 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 += '
Device connections.
'; + x += '
Device disconnections.
'; + x += '
Intel® AMT desktop and serial events.
'; + 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 // From bbb98c6c6d5ff63511c9cb8b5fd6a783f7f270c3 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 22 Jul 2019 16:00:43 -0700 Subject: [PATCH 2/2] Added new account invitation system. --- meshmail.js | 16 ++++++++++++++++ meshuser.js | 11 ++++++++++- package.json | 2 +- views/default-min.handlebars | 2 +- views/default.handlebars | 29 +++++++++++++++++++++++------ 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/meshmail.js b/meshmail.js index 02806410..d4580663 100644 --- a/meshmail.js +++ b/meshmail.js @@ -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'] = '[[[SERVERNAME]]] - Account Invitation\r\n
[[[SERVERNAME]]] - Account Invitation

An account was created for you on server [[[SERVERNAME]]], you can access it now with:

   Username: [[[ACCOUNTNAME]]]
   Password: [[[PASSWORD]]]

Best regards,
[[[USERNAME]]]
'; obj.mailTemplates['account-check.html'] = '[[[SERVERNAME]]] - Email Verification\r\n
[[[SERVERNAME]]] - Verification

Hi [[[USERNAME]]], [[[SERVERNAME]]] is requesting email verification, click on the following link to complete the process.

Click here to verify your e-mail address.

If you did not initiate this request, please ignore this mail.
'; obj.mailTemplates['account-reset.html'] = '[[[SERVERNAME]]] - Account Reset\r\n
[[[SERVERNAME]]] - Verification

Hi [[[USERNAME]]], [[[SERVERNAME]]] is requesting an account password reset, click on the following link to complete the process.

Click here to reset your account password.

If you did not initiate this request, please ignore this mail.
'; obj.mailTemplates['mesh-invite.html'] = '[[[SERVERNAME]]] - Invitation\r\n
[[[SERVERNAME]]] - Agent Installation
[[[AREA-NAME]]]

Hello [[[NAME]]],

[[[/AREA-NAME]]]

User [[[USERNAME]]] on server [[[SERVERNAME]]] is requesting you to install software to start a remote control session.

[[[AREA-MSG]]]

Message: [[[MSG]]]

[[[/AREA-MSG]]][[[AREA-WINDOWS]]]

Click here to download the MeshAgent for Windows.

[[[/AREA-WINDOWS]]][[[AREA-OSX]]]

Click here to download the MeshAgent for Apple OSX.

[[[/AREA-OSX]]][[[AREA-LINUX]]]

For Linux, cut & paste the following in a terminal to install the agent:

wget -q [[[SERVERURL]]]/meshagents?script=1 --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh [[[SERVERURL]]] \'[[[MESHIDHEX]]]\'

[[[/AREA-LINUX]]][[[AREA-LINK]]]

To install the software, click here and follow the instructions.

[[[/AREA-LINK]]]

If you did not initiate this request, please ignore this mail.

Best regards,
[[[USERNAME]]]
'; + + 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); diff --git a/meshuser.js b/meshuser.js index e26b071a..1d66b12c 100644 --- a/meshuser.js +++ b/meshuser.js @@ -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 { @@ -2857,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; }; \ No newline at end of file diff --git a/package.json b/package.json index 24fbcb18..dd2675dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.3.8-h", + "version": "0.3.8-i", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 64666d70..13b72073 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 99994c7c..c1ab3031 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -7365,8 +7365,12 @@ x += addHtmlValue('Email', ''); x += addHtmlValue('Password', ''); x += addHtmlValue('Password', ''); - x += '
Force password reset on next login.
'; - if (serverinfo.emailcheck) { x += '
Email is verified.
'; } + x += '
Randomize the password.
'; + x += '
Force password reset on next login.
'; + if (serverinfo.emailcheck) { + x += '
Email is verified.
'; + x += '
Send invitation email.
'; + } if (passRequirements) { var r = [], rc = 0; @@ -7380,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); }