Added public agent install invitation link support.

Ylian Saint-Hilaire committed Jun 3, 2019 at 13:15 UTC b676ab7e163ab5464c297d90c1b53f09317b0461
13 files changed +408 -20
MeshCentralServer.njsproj
+1
@@ -260,6 +260,7 @@
260 <Content Include="readme.txt" />
261 <Content Include="sample-config.json" />
262 <Content Include="SourceFileList.txt" />
263 + <Content Include="views\agentinvite.handlebars" />
264 <Content Include="views\default-min.handlebars" />
265 <Content Include="views\default-mobile-min.handlebars" />
266 <Content Include="views\default-mobile.handlebars" />
agents/MeshService-signed.exe
Binary files a/agents/MeshService-signed.exe and b/agents/MeshService-signed.exe differ
agents/MeshService.exe
Binary files a/agents/MeshService.exe and b/agents/MeshService.exe differ
agents/MeshService64-signed.exe
Binary files a/agents/MeshService64-signed.exe and b/agents/MeshService64-signed.exe differ
agents/MeshService64.exe
Binary files a/agents/MeshService64.exe and b/agents/MeshService64.exe differ
meshcentral.js
+16 -4
@@ -1549,8 +1549,14 @@ function CreateMeshCentralServer(config, args) {
1549 if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug(1, 'ERR: Bad cookie due to invalid time'); return null; }
1550 o.time = o.time * 1000; // Decode the cookie creation time
1551 o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created (in milliseconds)
1552 - if (timeout == null) { timeout = 2; }
1553 - if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
1552 + if ((o.expire) == null || (typeof o.expire != 'number')) {
1553 + // Use a fixed cookie expire time
1554 + if (timeout == null) { timeout = 2; }
1555 + if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
1556 + } else {
1557 + // An expire time is included in the cookie (in minutes), use this.
1558 + if ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
1559 + }
1560 return o;
1561 } catch (ex) { obj.debug(1, 'ERR: Bad AESGCM cookie due to exception: ' + ex); return null; }
1562 };
@@ -1571,8 +1577,14 @@ function CreateMeshCentralServer(config, args) {
1577 if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug(1, 'ERR: Bad cookie due to invalid time'); return null; }
1578 o.time = o.time * 1000; // Decode the cookie creation time
1579 o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created (in milliseconds)
1574 - if (timeout == null) { timeout = 2; }
1575 - if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
1580 + if ((o.expire) == null || (typeof o.expire != 'number')) {
1581 + // Use a fixed cookie expire time
1582 + if (timeout == null) { timeout = 2; }
1583 + if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
1584 + } else {
1585 + // An expire time is included in the cookie (in minutes), use this.
1586 + if ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right)
1587 + }
1588 return o;
1589 } catch (ex) { obj.debug(1, 'ERR: Bad AESSHA cookie due to exception: ' + ex); return null; }
1590 };
meshuser.js
+11
@@ -2499,6 +2499,17 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2499
2500 break;
2501 }
2502 + case 'createInviteLink': {
2503 + if (common.validateString(command.meshid, 8, 128) == false) break; // Check the meshid
2504 + if (common.validateInt(command.expire, 1, 99999) == false) break; // Check the expire time in hours
2505 + if (common.validateInt(command.flags, 0, 256) == false) break; // Check the flags
2506 + var mesh = parent.meshes[command.meshid];
2507 + if (mesh == null) break;
2508 + const inviteCookie = parent.parent.encodeCookie({ a: 4, mid: command.meshid, f: command.flags, expire: command.expire * 60 }, parent.parent.loginCookieEncryptionKey);
2509 + if (inviteCookie == null) break;
2510 + ws.send(JSON.stringify({ action: 'createInviteLink', meshid: command.meshid, expire: command.expire, cookie: inviteCookie }));
2511 + break;
2512 + }
2513 default: {
2514 // Unknown user action
2515 console.log('Unknown action from user ' + user.name + ': ' + command.action + '.');
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.3.5-v",
3 + "version": "0.3.5-x",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
public/images/macosagent.png
Binary files /dev/null and b/public/images/macosagent.png differ
public/images/winagent.png
Binary files /dev/null and b/public/images/winagent.png differ
views/agentinvite.handlebars new
+293
@@ -0,0 +1,293 @@
1 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 +<html>
3 +<head>
4 + <meta http-equiv="X-UA-Compatible" content="IE=edge" />
5 + <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
6 + <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
7 + <meta name="apple-mobile-web-app-capable" content="yes" />
8 + <meta name="format-detection" content="telephone=no" />
9 + <link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
10 + <script type="text/javascript" src="scripts/common-0.0.1.js"></script>
11 + <title>MeshCentral - Agent Installation</title>
12 + <style>
13 + .tab {
14 + overflow: hidden;
15 + border: 1px solid #ccc;
16 + background-color: #f1f1f1;
17 + }
18 +
19 + .tab button {
20 + background-color: inherit;
21 + float: left;
22 + border: none;
23 + outline: none;
24 + cursor: pointer;
25 + padding: 14px 16px;
26 + transition: 0.3s;
27 + }
28 +
29 + .tab button:hover {
30 + background-color: #ddd;
31 + }
32 +
33 + .tab button.active {
34 + background-color: #ccc;
35 + }
36 +
37 + .tabcontent {
38 + display: none;
39 + padding: 6px 12px;
40 + border: 1px solid #ccc;
41 + border-top: none;
42 + }
43 + </style>
44 +</head>
45 +<body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="display:none;overflow:hidden">
46 + <div id="container">
47 + <!-- Begin Masthead -->
48 + <div id="masthead" class=noselect style="background:url(logo.png) 0px 0px;background-color:#036;background-repeat:no-repeat;height:66px;width:100%;overflow:hidden;">
49 + <div style="float:left; height: 66px; color:#c8c8c8; padding-left:20px; padding-top:8px">
50 + <strong><font style="font-size:46px; font-family: Arial, Helvetica, sans-serif;">{{{title}}}</font></strong>
51 + </div>
52 + <div style="float:left; height: 66px; color:#c8c8c8; padding-left:5px; padding-top:14px">
53 + <strong><font style="font-size:14px; font-family: Arial, Helvetica, sans-serif;">{{{title2}}}</font></strong>
54 + </div>
55 + <p id="logoutControl" style="color:white;font-size:11px;margin: 10px 10px 0;">{{{logoutControl}}}</p>
56 + </div>
57 + <div id="page_leftbar">
58 + <div style="height:16px"></div>
59 + </div>
60 + <div id=topbar class="noselect style3" style="height:24px;position:relative">
61 + <div id=uiMenuButton title="User interface selection" onclick="showUserInterfaceSelectMenu()">
62 + &diams;
63 + <div id=uiMenu style="display:none">
64 + <div id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface"><div class="uiSelector1"></div></div>
65 + <div id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface"><div class="uiSelector2"></div></div>
66 + <div id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface"><div class="uiSelector3"></div></div>
67 + <div id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode"><div class="uiSelector4"></div></div>
68 + </div>
69 + </div>
70 + </div>
71 + <div id="column_l" style="max-height:calc(100vh - 135px);overflow-y:auto">
72 + <h1>Agent Installation<span id="groupname"></span></h1>
73 + <p>
74 + You have been invited to install a software that will allow a remote operator to fully access your computer remotely including the desktop and files.
75 + Only follow the instructions below if this invitation was expected and you know who will be accessing your computer.
76 + Selecting your operation system and follow the instructions below.
77 + </p>
78 + <div>
79 + <div class="tab">
80 + <button id="twintab64" class="tablinks" onclick="openTab(event, 'wintab64')">Windows 64bit</button>
81 + <button id="twintab32" class="tablinks" onclick="openTab(event, 'wintab32')">Windows 32bit</button>
82 + <button id="tlinuxtab" class="tablinks" onclick="openTab(event, 'linuxtab')">Linux</button>
83 + <button id="tmacostab" class="tablinks" onclick="openTab(event, 'macostab')">MacOS</button>
84 + </div>
85 +
86 + <div id="wintab64" class="tabcontent" style="background-color:white;color:black">
87 + <h3>Microsoft&trade; Windows 64bit</h3>
88 + <p><a id="win64url">Download the software here</a>, run it and press "Install" or "Connect".</p>
89 + <div style="text-align:center">
90 + <img src="images/winagent.png" />
91 + </div>
92 + </div>
93 +
94 + <div id="wintab32" class="tabcontent" style="background-color:white;color:black">
95 + <h3>Microsoft&trade; Windows 32bit</h3>
96 + <p><a id="win32url">Download the software here</a>, run it and press "Install" or "Connect".</p>
97 + <div style="text-align:center">
98 + <img src="images/winagent.png" />
99 + </div>
100 + </div>
101 +
102 + <div id="linuxtab" class="tabcontent" style="background-color:white;color:black">
103 + <h3>Linux</h3>
104 + <p>To install, cut and paste the following command in a root terminal.</p>
105 + <div id="linuxinstall" style="font-family:courier,'courier new',monospace;margin-left:30px"></div>
106 + <input type="button" value="Copy to clipboard" style="margin-left:30px;margin-top:4px" onclick="copyToClipLinuxInstall()" />
107 + <p>To uninstall, cut and paste the following command as root.</p>
108 + <div id="unlinuxinstall" style="font-family:courier,'courier new',monospace;margin-left:30px"></div>
109 + <input type="button" value="Copy to clipboard" style="margin-left:30px;margin-top:4px" onclick="copyToClipLinuxUnInstall()" />
110 + <br /><br />
111 + </div>
112 +
113 + <div id="macostab" class="tabcontent" style="background-color:white;color:black">
114 + <h3>Apple&trade; MacOS</h3>
115 + <p><a id="macosurl">Download the installer here</a>, right click on it and select "Open", then follow the instructions.</p>
116 + <div style="text-align:center">
117 + <img src="images/macosagent.png" />
118 + </div>
119 + </div>
120 + </div>
121 + </div>
122 + <div id="footer">
123 + <table cellpadding="0" cellspacing="10" style="width: 100%">
124 + <tr>
125 + <td style="text-align:left"></td>
126 + <td style="text-align:right"></td>
127 + </tr>
128 + </table>
129 + </div>
130 + </div>
131 + <script>
132 + 'use strict';
133 + var uiMode = parseInt(getstore('uiMode', 1));
134 + var webPageStackMenu = false;
135 + var webPageFullScreen = true;
136 + var nightMode = (getstore('_nightMode', '0') == '1');
137 + var domain = "{{{domain}}}";
138 + var domainUrl = "{{{domainurl}}}";
139 + var meshid = "{{{meshid}}}";
140 + var serverPort = "{{{serverport}}}";
141 + var serverHttps = "{{{serverhttps}}}";
142 + var serverNoProxy = "{{{servernoproxy}}}";
143 + var installFlags = "{{{installflags}}}";
144 + var groupName = decodeURIComponent("{{{meshname}}}");
145 + if (groupName != '') { QH('groupname', ' - ' + groupName); }
146 + userInterfaceSelectMenu();
147 + setup();
148 +
149 + // Toggle user interface menu
150 + function showUserInterfaceSelectMenu() {
151 + Q('uiViewButton1').classList.remove('uiSelectorSel');
152 + Q('uiViewButton2').classList.remove('uiSelectorSel');
153 + Q('uiViewButton3').classList.remove('uiSelectorSel');
154 + Q('uiViewButton4').classList.remove('uiSelectorSel');
155 + try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
156 + QV('uiMenu', (QS('uiMenu').display == 'none'));
157 + if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
158 + }
159 +
160 + function userInterfaceSelectMenu(s) {
161 + if (s) { uiMode = s; putstore('uiMode', uiMode); }
162 + webPageFullScreen = (uiMode < 3);
163 + webPageStackMenu = true;//(uiMode > 1);
164 + toggleFullScreen(0);
165 + toggleStackMenu(0);
166 + QC('column_l').add('room4submenu');
167 + }
168 +
169 + function toggleNightMode() {
170 + nightMode = !nightMode;
171 + if (nightMode) { QC('body').add('night'); } else { QC('body').remove('night'); }
172 + putstore('_nightMode', nightMode ? '1' : '0');
173 + }
174 +
175 + // Toggle the web page to full screen
176 + function toggleFullScreen(toggle) {
177 + if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
178 + var hide = 0;
179 + //if (args.hide) { hide = parseInt(args.hide); }
180 + if (webPageFullScreen == false) {
181 + QC('body').remove("menu_stack");
182 + QC('body').remove("fullscreen");
183 + QC('body').remove("arg_hide");
184 + //if (xxcurrentView >= 10) QC('column_l').add('room4submenu');
185 + //QV('UserDummyMenuSpan', false);
186 + //QV('page_leftbar', false);
187 + } else {
188 + QC('body').add("fullscreen");
189 + if (hide & 16) QC('body').add("arg_hide"); // This is replacement for QV('page_leftbar', !(hide & 16));
190 + //QV('UserDummyMenuSpan', (xxcurrentView < 10) && webPageFullScreen);
191 + //QV('page_leftbar', true);
192 + }
193 + QV('body', true);
194 + }
195 +
196 + // If FullScreen, toggle menu to be horisontal or vertical
197 + function toggleStackMenu(toggle) {
198 + if (webPageFullScreen == true) {
199 + if (toggle === 1) {
200 + webPageStackMenu = !webPageStackMenu;
201 + putstore('webPageStackMenu', webPageStackMenu);
202 + }
203 + if (webPageStackMenu == false) {
204 + QC('body').remove("menu_stack");
205 + } else {
206 + QC('body').add("menu_stack");
207 + //if (xxcurrentView >= 10) QC('column_l').remove('room4submenu');
208 + }
209 + }
210 + }
211 +
212 + function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
213 + function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
214 +
215 + function openTab(evt, tabname) {
216 + // Declare all variables
217 + var i, tabcontent, tablinks;
218 +
219 + // Get all elements with class="tabcontent" and hide them
220 + tabcontent = document.getElementsByClassName("tabcontent");
221 + for (i = 0; i < tabcontent.length; i++) {
222 + tabcontent[i].style.display = "none";
223 + }
224 +
225 + // Get all elements with class="tablinks" and remove the class "active"
226 + tablinks = document.getElementsByClassName("tablinks");
227 + for (i = 0; i < tablinks.length; i++) {
228 + tablinks[i].className = tablinks[i].className.replace(" active", "");
229 + }
230 +
231 + // Show the current tab, and add an "active" class to the button that opened the tab
232 + document.getElementById(tabname).style.display = "block";
233 + if (evt != null) { evt.currentTarget.className += " active"; } else { document.getElementById('t' + tabname).className += " active"; }
234 + }
235 +
236 + var linuxInstall, linuxUnInstall;
237 + function setup() {
238 + var servername = window.location.hostname;
239 + var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
240 +
241 + // Windows 64bit Setup
242 + var url = 'meshagents?id=4&meshid=' + meshid;
243 + if (installFlags != 0) { url += ('&installflags=' + installFlags); }
244 + Q('win64url').href = url;
245 +
246 + // Windows 32bit Setup
247 + url = 'meshagents?id=3&meshid=' + meshid;
248 + if (installFlags != 0) { url += ('&installflags=' + installFlags); }
249 + Q('win32url').href = url;
250 +
251 + // MacOS Setup
252 + url = 'meshagents?id=16&meshid=' + meshid;
253 + Q('macosurl').href = url;
254 +
255 + // Linux Setup
256 + if (serverHttps == 1) {
257 + var portStr = (serverPort == 443) ? '' : (":" + serverPort);
258 + if (serverNoProxy == 0) {
259 + linuxInstall = "(wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-check-certificate -O ./meshinstall.sh || wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh https://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
260 + linuxUnInstall = "(wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-check-certificate -O ./meshinstall.sh || wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
261 + } else {
262 + // Server asked that agent be installed to preferably not use a HTTP proxy.
263 + linuxInstall = "wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh https://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
264 + linuxUnInstall = "wget https://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy --no-check-certificate -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
265 + }
266 + } else {
267 + var portStr = (serverPort == 80) ? '' : (":" + serverPort);
268 + if (serverNoProxy == 0) {
269 + linuxInstall = "(wget http://" + servername + portStr + domainUrl + "meshagents?script=1 -O ./meshinstall.sh || wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh http://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
270 + linuxUnInstall = "(wget http://" + servername + portStr + domainUrl + "meshagents?script=1 -O ./meshinstall.sh || wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
271 + } else {
272 + // Server asked that agent be installed to preferably not use a HTTP proxy.
273 + linuxInstall = "wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh http://" + servername + portStr + domainUrlNoSlash + " '" + meshid + "'\r\n";
274 + linuxUnInstall = "wget http://" + servername + portStr + domainUrl + "meshagents?script=1 --no-proxy -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n";
275 + }
276 + }
277 + QH('linuxinstall', linuxInstall);
278 + QH('unlinuxinstall', linuxUnInstall);
279 +
280 + // Attempt to detect the most likely operating system for this browser
281 + if (navigator.userAgent.indexOf('Win64')) { openTab(null, 'wintab64'); }
282 + else if (navigator.userAgent.indexOf('Windows')) { openTab(null, 'wintab32'); }
283 + else if (navigator.userAgent.indexOf('Linux')) { openTab(null, 'linuxtab'); }
284 + else if (navigator.userAgent.indexOf('Macintosh')) { openTab(null, 'macostab'); }
285 + else { openTab(null, 'wintab64'); }
286 + }
287 +
288 + function copyToClipLinuxInstall() { navigator.clipboard.writeText(linuxInstall); }
289 + function copyToClipLinuxUnInstall() { navigator.clipboard.writeText(linuxUnInstall); }
290 +
291 + </script>
292 +</body>
293 +</html>
views/default.handlebars
+61 -15
@@ -1977,6 +1977,28 @@
1977 }
1978 break;
1979 }
1980 + case 'createInviteLink': { // Agent installation invitation link
1981 + if (xxdialogTag != message.meshid) break;
1982 + var servername = serverinfo.name;
1983 + if ((servername.indexOf('.') == -1) || ((features & 2) != 0)) { servername = window.location.hostname; } // If the server name is not set or it's in LAN-only mode, use the URL hostname as server name.
1984 + var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
1985 + var url;
1986 + if (serverinfo.https == true) {
1987 + var portStr = (serverinfo.port == 443) ? '' : (":" + serverinfo.port);
1988 + url = "https://" + servername + portStr + domainUrl + "agentinvite?c=" + message.cookie;
1989 + } else {
1990 + var portStr = (serverinfo.port == 80) ? '' : (":" + serverinfo.port);
1991 + url = "http://" + servername + portStr + domainUrl + "agentinvite?c=" + message.cookie;
1992 + }
1993 + Q('agentInvitationLink').href = url;
1994 + var t = message.expire + ' hour' + addLetterS(message.expire);
1995 + if (message.expire == 24) { t = '1 day'; }
1996 + if (message.expire == 168) { t = '1 week'; }
1997 + if (message.expire == 5040) { t = '1 month'; }
1998 + QH('agentInvitationLink', 'Invitation Link (' + t + ')');
1999 + QV('agentInvitationLinkDiv', true);
2000 + break;
2001 + }
2002 case 'stopped': { // Server is stopping.
2003 // Disconnect
2004 autoReconnect = false;
@@ -2547,9 +2569,7 @@
2569 }
2570 if (mesh.mtype == 2) {
2571 r += ' <a style=cursor:pointer;font-size:10px title="Add a new computer to this mesh by installing the mesh agent." onclick=addAgentToMesh(\"' + mesh._id + '\")>Add Agent</a>';
2550 - if (features & 64) {
2551 - r += ' <a style=cursor:pointer;font-size:10px title="Invite someone to install the mesh agent on this mesh." onclick=inviteAgentToMesh(\"' + mesh._id + '\")>Invite</a>';
2552 - }
2572 + r += ' <a style=cursor:pointer;font-size:10px title="Invite someone to install the mesh agent on this mesh." onclick=inviteAgentToMesh(\"' + mesh._id + '\")>Invite</a>';
2573 }
2574 return r;
2575 }
@@ -2673,23 +2693,51 @@
2693
2694 function inviteAgentToMesh(meshid) {
2695 if (xxdialogMode) return;
2676 - var mesh = meshes[meshid];
2677 - var x = "Invite someone to install the mesh agent. An email with be sent with the link to the mesh agent installation for " + EscapeHtml(mesh.name) + ".<br /><br />";
2678 - x += addHtmlValue('Name (optional)', '<input id=agentInviteName value="" style=width:230px maxlength=64 />');
2679 - x += addHtmlValue('Email', '<input id=agentInviteEmail style=width:230px placeholder="example@email.com" onkeyup=validateAgentInvite()></input>');
2680 - x += addHtmlValue('Operating System', '<select id=agentInviteNameOs style=width:236px><option value=0>Any supported</option><option value=1>Windows only</option><option value=3>Apple MacOS only</option><option value=2>Linux only</option></select>');
2681 - 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>');
2682 - x += addHtmlValue('Message<br />(optional)', '<textarea id=agentInviteMessage value="" style=width:230px;height:100px;resize:none maxlength=1024 /></textarea>');
2696 + var x = '', mesh = meshes[meshid];
2697 + if (features & 64) {
2698 + x += addHtmlValue('Invitation Type', '<select id=d2InviteType onchange=d2ChangedInviteType() style=width:236px><option value=0>Link invitation</option><option value=1>Email invitation</option></select>') + "<hr />";
2699 + x += "<div id=emailInviteDiv style=display:none>Invite someone to install the mesh agent. An email with be sent with the link to the mesh agent installation for the \"" + EscapeHtml(mesh.name) + "\" device group.<br /><br />";
2700 + x += addHtmlValue('Name (optional)', '<input id=agentInviteName value="" style=width:230px maxlength=64 />');
2701 + x += addHtmlValue('Email', '<input id=agentInviteEmail style=width:230px placeholder="example@email.com" onkeyup=validateAgentInvite()></input>');
2702 + x += addHtmlValue('Operating System', '<select id=agentInviteNameOs style=width:236px><option value=0>Any supported</option><option value=1>Windows only</option><option value=3>Apple MacOS only</option><option value=2>Linux only</option></select>');
2703 + 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>');
2704 + x += addHtmlValue('Message<br />(optional)', '<textarea id=agentInviteMessage value="" style=width:230px;height:100px;resize:none maxlength=1024 /></textarea>');
2705 + x += '</div>';
2706 + }
2707 + x += '<div id=urlInviteDiv>Invite someone to install the mesh agent by sharing a invitation link. This link points the user to installation instructions for the \"' + EscapeHtml(mesh.name) + '\" device group. The link is public and no account this server is needed.<br /><br />';
2708 + 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></select>');
2709 + x += '<div id=agentInvitationLinkDiv style="text-align:center;font-size:large;margin:16px;display:none"><a id=agentInvitationLink target="_blank" href="" 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>';
2710 setDialogMode(2, "Invite", 3, performAgentInvite, x, meshid);
2711 validateAgentInvite();
2712 + d2RequestInvitationLink();
2713 + }
2714 +
2715 + function d2RequestInvitationLink() {
2716 + meshserver.send({ action: 'createInviteLink', meshid: xxdialogTag, expire: parseInt(Q('d2inviteExpire').value), flags: 0 });
2717 + }
2718 +
2719 + function d2ChangedInviteType() {
2720 + QV('urlInviteDiv', Q('d2InviteType').value == 0);
2721 + if (features & 64) { QV('emailInviteDiv', Q('d2InviteType').value == 1); }
2722 + validateAgentInvite();
2723 }
2724
2725 + function d2CopyInviteToClip() { navigator.clipboard.writeText(Q('agentInvitationLink').href); }
2726 +
2727 function validateAgentInvite() {
2688 - QE('idx_dlgOkButton', checkEmail(Q('agentInviteEmail').value));
2728 + if ((features & 64) && (Q('d2InviteType').value == 1)) {
2729 + QE('idx_dlgOkButton', checkEmail(Q('agentInviteEmail').value));
2730 + QV('idx_dlgCancelButton', true);
2731 + } else {
2732 + QE('idx_dlgOkButton', true);
2733 + QV('idx_dlgCancelButton', false);
2734 + }
2735 }
2736
2737 function performAgentInvite(button, meshid) {
2692 - meshserver.send({ action: 'inviteAgent', meshid: meshid, email: Q('agentInviteEmail').value, name: Q('agentInviteName').value, os: Q('agentInviteNameOs').value, flags: Q('agentInviteType').value, msg: Q('agentInviteMessage').value });
2738 + if ((features & 64) && (Q('d2InviteType').value == 1)) {
2739 + meshserver.send({ action: 'inviteAgent', meshid: meshid, email: Q('agentInviteEmail').value, name: Q('agentInviteName').value, os: Q('agentInviteNameOs').value, flags: Q('agentInviteType').value, msg: Q('agentInviteMessage').value });
2740 + }
2741 }
2742
2743 function addAgentToMesh(meshid) {
@@ -6196,9 +6244,7 @@
6244 }
6245 if (currentMesh.mtype == 2) {
6246 x += '<a onclick=addAgentToMesh(\"' + currentMesh._id + '\") style=cursor:pointer;margin-right:10px title="Add a new computer to this mesh by installing the mesh agent."><img src=images/icon-addnew.png border=0 height=12 width=12> Install</a>';
6199 - if (features & 64) {
6200 - x += '<a onclick=inviteAgentToMesh(\"' + currentMesh._id + '\") style=cursor:pointer;margin-right:10px title="Invite someone to install the mesh agent on this mesh."><img src=images/icon-addnew.png border=0 height=12 width=12> Invite</a>';
6201 - }
6247 + x += '<a onclick=inviteAgentToMesh(\"' + currentMesh._id + '\") style=cursor:pointer;margin-right:10px title="Invite someone to install the mesh agent on this mesh."><img src=images/icon-addnew.png border=0 height=12 width=12> Invite</a>';
6248 }
6249 }
6250
webserver.js
+25
@@ -1063,6 +1063,30 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1063 }
1064 }
1065
1066 + // Called to process an agent invite request
1067 + function handleAgentInviteRequest(req, res) {
1068 + const domain = checkUserIpAddress(req, res);
1069 + if ((domain == null) || ((req.query.m == null) && (req.query.c == null))) { res.sendStatus(404); return; }
1070 + if (req.query.c != null) {
1071 + // A cookie is specified in the query string, use that
1072 + var cookie = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey);
1073 + if (cookie == null) { res.sendStatus(404); return; }
1074 + var mesh = obj.meshes[cookie.mid];
1075 + if (mesh == null) { res.sendStatus(404); return; }
1076 + var installflags = cookie.f;
1077 + if (typeof installflags != 'number') { installflags = 0; }
1078 + res.render(obj.path.join(obj.parent.webViewsPath, 'agentinvite'), { title: domain.title, title2: domain.title2, domainurl: domain.url, meshid: mesh._id.split('/')[2], serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: ((args.notls == true) ? '0' : '1'), servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name), installflags: installflags });
1079 + } else if (req.query.m != null) {
1080 + // The MeshId is specified in the query string, use that
1081 + var mesh = obj.meshes['mesh/' + domain.id + '/' + req.query.m.toLowerCase()];
1082 + if (mesh == null) { res.sendStatus(404); return; }
1083 + var installflags = 0;
1084 + if (req.query.f) { installflags = parseInt(req.query.f); }
1085 + if (typeof installflags != 'number') { installflags = 0; }
1086 + res.render(obj.path.join(obj.parent.webViewsPath, 'agentinvite'), { title: domain.title, title2: domain.title2, domainurl: domain.url, meshid: mesh._id.split('/')[2], serverport: ((args.aliasport != null) ? args.aliasport : args.port), serverhttps: ((args.notls == true) ? '0' : '1'), servernoproxy: ((domain.agentnoproxy === true) ? '1' : '0'), meshname: encodeURIComponent(mesh.name), installflags: installflags });
1087 + }
1088 + }
1089 +
1090 function handleDeleteAccountRequest(req, res) {
1091 const domain = checkUserIpAddress(req, res);
1092 if ((domain == null) || (domain.auth == 'sspi') || (domain.auth == 'ldap')) { res.sendStatus(404); return; }
@@ -2670,6 +2694,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2694 obj.app.post(url + 'resetpassword', handleResetPasswordRequest);
2695 obj.app.post(url + 'resetaccount', handleResetAccountRequest);
2696 obj.app.get(url + 'checkmail', handleCheckMailRequest);
2697 + obj.app.get(url + 'agentinvite', handleAgentInviteRequest);
2698 obj.app.post(url + 'amtevents.ashx', obj.handleAmtEventRequest);
2699 obj.app.get(url + 'meshagents', obj.handleMeshAgentRequest);
2700 obj.app.get(url + 'messenger', handleMessengerRequest);