diff --git a/meshrelay.js b/meshrelay.js index 5a11d19e..46f1bdac 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -13,6 +13,28 @@ /*jshint esversion: 6 */ "use strict"; +// Mesh Rights +const MESHRIGHT_EDITMESH = 0x00000001; +const MESHRIGHT_MANAGEUSERS = 0x00000002; +const MESHRIGHT_MANAGECOMPUTERS = 0x00000004; +const MESHRIGHT_REMOTECONTROL = 0x00000008; +const MESHRIGHT_AGENTCONSOLE = 0x00000010; +const MESHRIGHT_SERVERFILES = 0x00000020; +const MESHRIGHT_WAKEDEVICE = 0x00000040; +const MESHRIGHT_SETNOTES = 0x00000080; +const MESHRIGHT_REMOTEVIEWONLY = 0x00000100; +const MESHRIGHT_NOTERMINAL = 0x00000200; +const MESHRIGHT_NOFILES = 0x00000400; +const MESHRIGHT_NOAMT = 0x00000800; +const MESHRIGHT_DESKLIMITEDINPUT = 0x00001000; +const MESHRIGHT_LIMITEVENTS = 0x00002000; +const MESHRIGHT_CHATNOTIFY = 0x00004000; +const MESHRIGHT_UNINSTALL = 0x00008000; +const MESHRIGHT_NODESKTOP = 0x00010000; +const MESHRIGHT_REMOTECOMMAND = 0x00020000; +const MESHRIGHT_RESETOFF = 0x00040000; +const MESHRIGHT_GUESTSHARING = 0x00080000; +const MESHRIGHT_ADMIN = 0xFFFFFFFF; function checkDeviceSharePublicIdentifier(parent, domain, nodeid, pid, func) { // Check the public id @@ -166,7 +188,14 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { } } return false; - }; + } + + // Push any stored message to the peer + obj.pushStoredMessages = function () { + if ((obj.storedPushedMessages != null) && (this.peer != null)) { + for (var i in obj.storedPushedMessages) { try { this.peer.send(JSON.stringify({ action: 'chat', msg: obj.storedPushedMessages[i] })); } catch (ex) { } } + } + } // Send a PING/PONG message function sendPing() { @@ -277,6 +306,10 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { // Do not record the session, just send session start try { ws.send('c'); } catch (ex) { } // Send connect to both peers try { relayinfo.peer1.ws.send('c'); } catch (ex) { } + + // Send any stored push messages + obj.pushStoredMessages(); + relayinfo.peer1.pushStoredMessages(); return; } } @@ -300,6 +333,10 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { parent.parent.debug('relay', 'Relay: Unable to record to file: ' + recFullFilename); try { ws.send('c'); } catch (ex) { } // Send connect to both peers try { relayinfo.peer1.ws.send('c'); } catch (ex) { } + + // Send any stored push messages + obj.pushStoredMessages(); + relayinfo.peer1.pushStoredMessages(); } else { // Write the recording file header parent.parent.debug('relay', 'Relay: Started recoding to file: ' + recFullFilename); @@ -312,10 +349,17 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { try { relayinfo.peer1.ws.logfile = ws.logfile = logfile; } catch (ex) { try { ws.send('c'); } catch (ex) { } // Send connect to both peers, 'cr' indicates the session is being recorded. try { relayinfo.peer1.ws.send('c'); } catch (ex) { } + // Send any stored push messages + obj.pushStoredMessages(); + relayinfo.peer1.pushStoredMessages(); return; } try { ws.send('cr'); } catch (ex) { } // Send connect to both peers, 'cr' indicates the session is being recorded. try { relayinfo.peer1.ws.send('cr'); } catch (ex) { } + + // Send any stored push messages + obj.pushStoredMessages(); + relayinfo.peer1.pushStoredMessages(); }); } }); @@ -324,6 +368,10 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { // Send session start try { ws.send('c'); } catch (ex) { } // Send connect to both peers try { relayinfo.peer1.ws.send('c'); } catch (ex) { } + + // Send any stored push messages + obj.pushStoredMessages(); + relayinfo.peer1.pushStoredMessages(); } parent.parent.debug('relay', 'Relay connected: ' + obj.id + ' (' + obj.req.clientIp + ' --> ' + obj.peer.req.clientIp + ')'); @@ -348,9 +396,29 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { } } else { // Wait for other relay connection - ws._socket.pause(); // Hold traffic until the other connection + if ((obj.id.startsWith('meshmessenger/node/') == true) && obj.authenticated && (parent.parent.firebase != null)) { + // This is an authenticated messenger session, push messaging may be allowed. Don't hold traffic. + ws._socket.resume(); // Don't hold traffic, process push messages + parent.parent.debug('relay', 'Relay messenger waiting: ' + obj.id + ' (' + obj.req.clientIp + ') ' + (obj.authenticated ? 'Authenticated' : '')); + + // Fetch the Push Messaging Token + const idsplit = obj.id.split('/'); + const nodeid = idsplit[1] + '/' + idsplit[2] + '/' + idsplit[3]; + parent.db.Get(nodeid, function (err, nodes) { + if ((err == null) && (nodes != null) && (nodes.length == 1) && (typeof nodes[0].pmt == 'string')) { + if ((parent.GetNodeRights(obj.user, nodes[0].meshid, nodes[0]._id) & MESHRIGHT_CHATNOTIFY) != 0) { + obj.pmt = nodes[0].pmt; + obj.nodename = nodes[0].name; + // Create the peer connection URL, we will include that in push messages + obj.msgurl = req.headers.origin + (req.url.split('/.websocket')[0].split('/meshrelay.ashx').join('/messenger')) + '?id=' + req.query.id + } + } + }); + } else { + ws._socket.pause(); // Hold traffic until the other connection + parent.parent.debug('relay', 'Relay holding: ' + obj.id + ' (' + obj.req.clientIp + ') ' + (obj.authenticated ? 'Authenticated' : '')); + } parent.wsrelays[obj.id] = { peer1: obj, state: 1, timeout: setTimeout(closeBothSides, 30000) }; - parent.parent.debug('relay', 'Relay holding: ' + obj.id + ' (' + obj.req.clientIp + ') ' + (obj.authenticated ? 'Authenticated' : '')); // Check if a peer server has this connection if (parent.parent.multiServer != null) { @@ -372,7 +440,6 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { // When data is received from the mesh relay web socket ws.on('message', function (data) { - //console.log(typeof data, data.length); if (this.peer != null) { //if (typeof data == 'string') { console.log('Relay: ' + data); } else { console.log('Relay:' + data.length + ' byte(s)'); } if (this.peer.slowRelay == null) { @@ -403,6 +470,31 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) { } } catch (ex) { console.log(ex); } } + } else { + if ((typeof data == 'string') && (obj.pmt != null)) { + var command = null; + try { command = JSON.parse(data); } catch (ex) { return; } + if ((typeof command != 'object') || (command.action != 'chat') || (typeof command.msg != 'string') || (command.msg == '')) return; + + // Store pushed messages + if (obj.storedPushedMessages == null) { obj.storedPushedMessages = []; } + obj.storedPushedMessages.push(obj.storedPushedMessages.push(command.msg)); + while (obj.storedPushedMessages.length > 50) { obj.storedPushedMessages.shift(); } // Only keep last 50 notifications + + // Send out a push message to the device + command.title = (domain.title ? domain.title : 'MeshCentral'); + var payload = { notification: { title: command.title, body: command.msg }, data: { url: obj.msgurl } }; + var options = { priority: 'High', timeToLive: 5 * 60 }; // TTL: 5 minutes, priority 'Normal' or 'High' + parent.parent.firebase.messaging().sendToDevice(obj.pmt, payload, options) + .then(function (response) { + parent.parent.debug('email', 'Successfully send push message to device ' + obj.nodename + ', title: ' + command.title + ', msg: ' + command.msg); + try { ws.send(JSON.stringify({ action: 'ctrl', value: 1 })); } catch (ex) { } // Push notification success + }) + .catch(function (error) { + parent.parent.debug('email', 'Failed to send push message to device ' + obj.nodename + ', title: ' + command.title + ', msg: ' + command.msg + ', error: ' + error); + try { ws.send(JSON.stringify({ action: 'ctrl', value: 2 })); } catch (ex) { } // Push notification failed + }); + } } }); diff --git a/meshuser.js b/meshuser.js index 0843cad5..260bfbc2 100644 --- a/meshuser.js +++ b/meshuser.js @@ -5203,7 +5203,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use db.Get(command.nodeid, function (err, nodes) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???) if ((err == null) && (nodes.length == 1)) { const node = nodes[0]; - if (((parent.GetMeshRights(user, node.meshid) & MESHRIGHT_REMOTECONTROL) != 0) && (typeof node.pmt == 'string')) { + if (((parent.GetNodeRights(user, node.meshid, node._id) & MESHRIGHT_CHATNOTIFY) != 0) && (typeof node.pmt == 'string')) { // Send out a push message to the device var payload = { notification: { title: command.title, body: command.msg } }; var options = { priority: "Normal", timeToLive: 5 * 60 }; // TTL: 5 minutes @@ -5219,6 +5219,31 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use }); break; } + case 'pushconsole': { + // Check if this user has rights on this nodeid + if (parent.parent.firebase == null) return; + if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid + if (common.validateString(command.console, 1, 3000) == false) break; // Check console command + db.Get(command.nodeid, function (err, nodes) { // TODO: Make a NodeRights(user) method that also does not do a db call if agent is connected (???) + if ((err == null) && (nodes.length == 1)) { + const node = nodes[0]; + if ((parent.GetNodeRights(user, node.meshid, node._id) == MESHRIGHT_ADMIN) && (typeof node.pmt == 'string')) { + // Send out a push message to the device + var payload = { data: { console: command.console, session: ws.sessionId } }; + var options = { priority: "Normal", timeToLive: 60 }; // TTL: 1 minutes, priority 'Normal' or 'High' + parent.parent.firebase.messaging().sendToDevice(node.pmt, payload, options) + .then(function (response) { + try { ws.send(JSON.stringify({ action: 'msg', type: 'console', nodeid: node._id, value: 'OK' })); } catch (ex) { } + }) + .catch(function (error) { + try { ws.send(JSON.stringify({ action: 'msg', type: 'console', nodeid: node._id, value: 'Failed: ' + error })); } catch (ex) { } + parent.parent.debug('email', 'Failed to send push console message to device ' + node.name + ', command: ' + command.console + ', error: ' + error); + }); + } + } + }); + break; + } case 'print': { console.log(command.value); break; diff --git a/views/default.handlebars b/views/default.handlebars index 78a69408..5678851c 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -823,9 +823,10 @@