Improved agent invitations, #3694

Ylian Saint-Hilaire committed Feb 23, 2022 at 16:14 UTC e58419a6c3ffde82ac22a250862b5d5871813970
4 files changed +54 -10
meshuser.js
+4 -1
@@ -2136,6 +2136,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2136 return;
2137 }
2138 mesh.invite = { codes: command.invite.codes, flags: command.invite.flags };
2139 + if (typeof command.invite.ag == 'number') { mesh.invite.ag = command.invite.ag; }
2140 if (change != '') { change += ' and invite code changed'; } else { change += 'Device group "' + mesh.name + '" invite code changed'; }
2141 changesids.push(6);
2142 }
@@ -3681,7 +3682,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3682 break;
3683 }
3684
3684 - const inviteCookie = parent.parent.encodeCookie({ a: 4, mid: command.meshid, f: command.flags, expire: command.expire * 60 }, parent.parent.invitationLinkEncryptionKey);
3685 + const cookie = { a: 4, mid: command.meshid, f: command.flags, expire: command.expire * 60 };
3686 + if ((typeof command.agents == 'number') && (command.agents != 0)) { cookie.ag = command.agents; }
3687 + const inviteCookie = parent.parent.encodeCookie(cookie, parent.parent.invitationLinkEncryptionKey);
3688 if (inviteCookie == null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createInviteLink', responseid: command.responseid, result: 'Unable to generate invitation cookie' })); } catch (ex) { } } break; }
3689
3690 // Create the server url
views/agentinvite.handlebars
+24 -1
@@ -85,7 +85,7 @@
85 <button id="tlinuxtab" class="tablinks" onclick="openTab(event, 'linuxtab')">Linux</button>
86 <button id="tmacostab" class="tablinks" onclick="openTab(event, 'macostab')">MacOS</button>
87 <button id="tandrotab" class="tablinks" onclick="openTab(event, 'androtab')">Android</button>
88 - <button id="tandrotab" class="tablinks" onclick="openTab(event, 'assistab')">Assistant</button>
88 + <button id="tassistab" class="tablinks" onclick="openTab(event, 'assistab')">Assistant</button>
89 </div>
90
91 <div id="wintab64" class="tabcontent" style="background-color:white;color:black">
@@ -180,6 +180,7 @@
180 var serverHttps = '{{{serverhttps}}}';
181 var serverNoProxy = '{{{servernoproxy}}}';
182 var installFlags = '{{{installflags}}}';
183 + var showAgents = parseInt('{{{showagents}}}'); // 0 = Show all agents, 1 = Windows only, 2 = Linux only, 4 = MacOS only, 8 = Assistant only, 16 = Android
184 var magenturl = '{{{magenturl}}}';
185 var groupName = decodeURIComponent('{{{meshname}}}');
186 var urlargs = parseUriArgs();
@@ -215,8 +216,30 @@
216 document.title = "Agent Installation";
217 }
218
219 + // Setup visible tabs
220 + var tabcount = 0, tabselect = null;
221 + var tab1 = (showAgents == 0) || (showAgents & 1);
222 + var tab2 = (showAgents == 0) || (showAgents & 1);
223 + var tab3 = (showAgents == 0) || (showAgents & 2);
224 + var tab4 = (showAgents == 0) || (showAgents & 4);
225 + var tab5 = (showAgents == 0) || (showAgents & 16);
226 + var tab6 = (showAgents == 0) || (showAgents & 8);
227 + if (tab6) { tabcount++; tabselect = 'assistab'; }
228 + if (tab5) { tabcount++; tabselect = 'androtab'; }
229 + if (tab4) { tabcount++; tabselect = 'macostab'; }
230 + if (tab3) { tabcount++; tabselect = 'linuxtab'; }
231 + if (tab2) { tabcount++; tabselect = 'wintab32'; }
232 + if (tab1) { tabcount++; tabselect = 'wintab64'; }
233 + QV('twintab64', tab1 && (tabcount > 1));
234 + QV('twintab32', tab2 && (tabcount > 1));
235 + QV('tlinuxtab', tab3 && (tabcount > 1));
236 + QV('tmacostab', tab4 && (tabcount > 1));
237 + QV('tandrotab', tab5 && (tabcount > 1));
238 + QV('tassistab', tab6 && (tabcount > 1));
239 +
240 userInterfaceSelectMenu();
241 setup();
242 + openTab(null, tabselect);
243 }
244
245 // Create the QR code
views/default.handlebars
+18 -5
@@ -4962,7 +4962,10 @@
4962 }
4963 x += '<div id=urlInviteDiv>' + format("Invite someone to install the mesh agent by sharing an invitation link. This link points the user to installation instructions for the \"{0}\" device group. The link is public and no account for this server is needed.", EscapeHtml(mesh.name)) + '<br /><br />';
4964 x += addHtmlValue("Link Expiration", '<select id=d2inviteExpire style=width:236px onchange=d2RequestInvitationLink()><option value=1>' + "1 hour" + '</option><option value=8>' + "8 hours" + '</option><option value=24>' + "1 day" + '</option><option value=168>' + "1 week" + '</option><option value=5040>' + "1 month" + '</option><option value=0>' + "Unlimited" + '</option></select>');
4965 + x += addHtmlValue("Agents", '<select id=d2agentType style=width:236px onchange=d2ChangedInviteType()><option value=0>' + "All Available Agents" + '</option><option value=1>' + "Windows MeshAgent" + '</option><option value=2>' + "Linux MeshAgent" + '</option><option value=4>' + "MacOS MeshAgent" + '</option><option value=8>' + "MeshCentral Assistant" + '</option></select>');
4966 + x += '<div id=d2agentInstallTypeDiv>';
4967 x += addHtmlValue("Installation Type", '<select id=d2agentInviteType style=width:236px onchange=d2RequestInvitationLink()><option value=0>' + "Background and interactive" + '</option><option value=2>' + "Background only" + '</option><option value=1>' + "Interactive only" + '</option></select>');
4968 + x += '</div>';
4969 x += '<div id=agentInvitationLinkDiv style="text-align:center;font-size:large;margin:16px;display:none"><a href=# id=agentInvitationLink rel="noreferrer noopener" target="_blank" style=cursor:pointer></a> <img src=images/link4.png height=10 width=10 title="' + "Copy link to clipboard" + '" style=cursor:pointer onclick=d2CopyInviteToClip()></div></div>';
4970 setDialogMode(2, "Invite", 3, performAgentInvite, x, meshid);
4971 if (features & 64) { Q('d2InviteType').focus(); d2ChangedInviteType(); } else { Q('d2inviteExpire').focus(); validateAgentInvite(); }
@@ -4971,14 +4974,18 @@
4974 }
4975
4976 function d2RequestInvitationLink() {
4974 - meshserver.send({ action: 'createInviteLink', meshid: xxdialogTag, expire: parseInt(Q('d2inviteExpire').value), flags: parseInt(Q('d2agentInviteType').value) });
4977 + meshserver.send({ action: 'createInviteLink', meshid: xxdialogTag, expire: parseInt(Q('d2inviteExpire').value), flags: parseInt(Q('d2agentInviteType').value), agents: parseInt(Q('d2agentType').value) });
4978 }
4979
4980 function d2ChangedInviteType() {
4978 - QV('urlInviteDiv', Q('d2InviteType').value == 0);
4979 - QV('d2agentexpirediv', Q('agentInviteNameOs').value == 4);
4980 - QV('emailInviteDiv', Q('d2InviteType').value == 1);
4981 + if (features & 64) {
4982 + QV('urlInviteDiv', Q('d2InviteType').value == 0);
4983 + QV('d2agentexpirediv', Q('agentInviteNameOs').value == 4);
4984 + QV('emailInviteDiv', Q('d2InviteType').value == 1);
4985 + }
4986 + QV('d2agentInstallTypeDiv', parseInt(Q('d2agentType').value) < 2);
4987 validateAgentInvite();
4988 + d2RequestInvitationLink();
4989 }
4990
4991 function d2CopyInviteToClip() { copyTextToClip(Q('agentInvitationLink').href); }
@@ -12758,11 +12765,15 @@
12765 x += '<div style=width:100%;text-align:center><a rel="noreferrer noopener" target=_blank href="' + url + '">' + url + '</a></div><br />';
12766 x += '<div style=margin-bottom:5px><label><input id=agentJoinCheck type=checkbox onclick=p20editmeshInviteCodeValidate() />' + "Enable Invite Codes" + '</label></div>';
12767 x += addHtmlValue("Invite Codes", '<input id=agentInviteCode style=width:236px onkeyup=p20editmeshInviteCodeValidate() placeholder="code1, code2, code3" />');
12768 + x += addHtmlValue("Agents", '<select id=agentType style=width:236px onchange=p20editmeshInviteCodeValidate()><option value=0>' + "All Available Agents" + '</option><option value=1>' + "Windows MeshAgent" + '</option><option value=2>' + "Linux MeshAgent" + '</option><option value=4>' + "MacOS MeshAgent" + '</option><option value=8>' + "MeshCentral Assistant" + '</option></select>');
12769 + x += '<div id=d2agentInstallTypeDiv>';
12770 x += addHtmlValue("Installation Type", '<select id=agentInviteType style=width:236px><option value=0>' + "Background and interactive" + '</option><option value=2>' + "Background only" + '</option><option value=1>' + "Interactive only" + '</option></select>');
12771 + x += '</div>';
12772 setDialogMode(2, "Invite Codes", 3, p20editmeshInviteCodeEx, x);
12773 if (currentMesh.invite != null) {
12774 Q('agentJoinCheck').checked = true;
12775 Q('agentInviteCode').value = currentMesh.invite.codes.join(', ');
12776 + if (currentMesh.invite.ag) { Q('agentType').value = currentMesh.invite.ag; }
12777 Q('agentInviteType').value = (currentMesh.invite.flags & 3);
12778 }
12779 p20editmeshInviteCodeValidate();
@@ -12780,15 +12791,17 @@
12791 var ok = true, codes = Q('agentInviteCode').value.split(',');
12792 for (var i in codes) { codes[i] = codes[i].trim(); if (codes[i] == '') { ok = false; } }
12793 QE('agentInviteCode', Q('agentJoinCheck').checked);
12794 + QE('agentType', Q('agentJoinCheck').checked);
12795 QE('agentInviteType', Q('agentJoinCheck').checked);
12796 QE('idx_dlgOkButton', (Q('agentJoinCheck').checked == false) || (ok));
12797 + QV('d2agentInstallTypeDiv', parseInt(Q('agentType').value) < 2);
12798 }
12799
12800 function p20editmeshInviteCodeEx() {
12801 if (Q('agentJoinCheck').checked == true) {
12802 var codes = Q('agentInviteCode').value.split(',');
12803 for (var i in codes) { codes[i] = codes[i].trim(); }
12791 - meshserver.send({ action: 'editmesh', meshid: currentMesh._id, invite: { codes: codes, flags: parseInt(Q('agentInviteType').value) } });
12804 + meshserver.send({ action: 'editmesh', meshid: currentMesh._id, invite: { codes: codes, flags: parseInt(Q('agentInviteType').value), ag: parseInt(Q('agentType').value) } });
12805 } else {
12806 meshserver.send({ action: 'editmesh', meshid: currentMesh._id, invite: '*' });
12807 }
webserver.js
+8 -3
@@ -1895,7 +1895,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
1895 for (var i in obj.meshes) {
1896 if ((obj.meshes[i].domain == domain.id) && (obj.meshes[i].deleted == null) && (obj.meshes[i].invite != null) && (obj.meshes[i].invite.codes.indexOf(req.body.inviteCode) >= 0)) {
1897 // Send invitation link, valid for 1 minute.
1898 - res.redirect(domain.url + 'agentinvite?c=' + parent.encodeCookie({ a: 4, mid: i, f: obj.meshes[i].invite.flags, expire: 1 }, parent.invitationLinkEncryptionKey) + (req.query.key ? ('&key=' + req.query.key) : '') + (req.query.hide ? ('&hide=' + req.query.hide) : ''));
1898 + res.redirect(domain.url + 'agentinvite?c=' + parent.encodeCookie({ a: 4, mid: i, f: obj.meshes[i].invite.flags, ag: obj.meshes[i].invite.ag, expire: 1 }, parent.invitationLinkEncryptionKey) + (req.query.key ? ('&key=' + req.query.key) : '') + (req.query.hide ? ('&hide=' + req.query.hide) : ''));
1899 return;
1900 }
1901 }
@@ -2045,6 +2045,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2045 if (mesh == null) { res.sendStatus(404); return; }
2046 var installflags = cookie.f;
2047 if (typeof installflags != 'number') { installflags = 0; }
2048 + var showagents = cookie.ag;
2049 + if (typeof showagents != 'number') { showagents = 0; }
2050 parent.debug('web', 'handleAgentInviteRequest using cookie.');
2051
2052 // Build the mobile agent URL, this is used to connect mobile devices
@@ -2057,7 +2059,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2059 var magenturl = 'mc://' + agentServerName + ((agentHttpsPort != 443) ? (':' + agentHttpsPort) : '') + ((xdomain != '') ? ('/' + xdomain) : '') + ',' + obj.agentCertificateHashBase64 + ',' + mesh._id.split('/')[2];
2060
2061 var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
2060 - render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, magenturl: magenturl }, req, domain));
2062 + render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, showagents: showagents, magenturl: magenturl }, req, domain));
2063 } else if (req.query.m != null) {
2064 // The MeshId is specified in the query string, use that
2065 var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.m.toLowerCase()];
@@ -2065,6 +2067,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2067 var installflags = 0;
2068 if (req.query.f) { installflags = parseInt(req.query.f); }
2069 if (typeof installflags != 'number') { installflags = 0; }
2070 + var showagents = 0;
2071 + if (req.query.f) { showagents = parseInt(req.query.ag); }
2072 + if (typeof showagents != 'number') { showagents = 0; }
2073 parent.debug('web', 'handleAgentInviteRequest using meshid.');
2074
2075 // Build the mobile agent URL, this is used to connect mobile devices
@@ -2077,7 +2082,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
2082 var magenturl = 'mc://' + agentServerName + ((agentHttpsPort != 443) ? (':' + agentHttpsPort) : '') + ((xdomain != '') ? ('/' + xdomain) : '') + ',' + obj.agentCertificateHashBase64 + ',' + mesh._id.split('/')[2];
2083
2084 var meshcookie = parent.encodeCookie({ m: mesh._id.split('/')[2] }, parent.invitationLinkEncryptionKey);
2080 - render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, magenturl: magenturl }, req, domain));
2085 + render(req, res, getRenderPage('agentinvite', req, domain), getRenderArgs({ meshid: meshcookie, serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: 1, servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name).replace(/'/g, '%27'), installflags: installflags, showagents: showagents, magenturl: magenturl }, req, domain));
2086 }
2087 }
2088