diff --git a/meshagent.js b/meshagent.js index 63de67c8..92ca6086 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1584,6 +1584,9 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (JSON.stringify(device.wsc) != JSON.stringify(command.wsc)) { /*changes.push('Windows Security Center status');*/ device.wsc = command.wsc; change = 1; log = 1; } } + // Push Messaging Token + if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) { device.pmt = command.pmt; change = 1; } // Don't save this as an event to the db. + if ((command.users != null) && (Array.isArray(command.users)) && (device.users != command.users)) { device.users = command.users; change = 1; } // Don't save this to the db. if ((mesh.mtype == 2) && (!args.wanonly)) { // In WAN mode, the hostname of a computer is not important. Don't log hostname changes. diff --git a/meshcentral.js b/meshcentral.js index d513962f..32d782d8 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -678,7 +678,7 @@ function CreateMeshCentralServer(config, args) { } // Check top level configuration for any unreconized values - if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'sendgrid', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".'); } } } + if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'sendgrid', 'firebase', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".'); } } } if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { config.settings.userallowedip = obj.args.userallowedip = null; } else { config.settings.userallowedip = obj.args.userallowedip = obj.args.userallowedip.split(','); } } if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { config.settings.userblockedip = obj.args.userblockedip = null; } else { config.settings.userblockedip = obj.args.userblockedip = obj.args.userblockedip.split(','); } } @@ -1544,6 +1544,18 @@ function CreateMeshCentralServer(config, args) { if ((obj.smsserver != null) && (obj.args.lanonly == true)) { addServerWarning("SMS gateway has limited use in LAN mode."); } } + // Setup Firebase + if (config.firebase != null) { + try { + obj.firebase = require('firebase-admin'); + var firebaseServiceAccount = require(obj.path.join(obj.datapath, config.firebase.serviceaccountkeyfile)); + obj.firebase.initializeApp({ credential: obj.firebase.credential.cert(firebaseServiceAccount) }); + } catch (ex) { + console.log('Unable to setup Firebase: ' + ex); + delete obj.firebase; + } + } + // Start periodic maintenance obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour @@ -3053,6 +3065,9 @@ function mainStart() { if (NodeJSVer < 8) { console.log("SMS Plivo support requires Node v8 or above, current version is " + process.version + "."); } else { modules.push('plivo'); } } + // Firebase Support + if (config.firebase != null) { modules.push('firebase-admin'); } + // Syslog support if ((require('os').platform() != 'win32') && (config.settings.syslog || config.settings.syslogjson)) { modules.push('modern-syslog'); } diff --git a/meshuser.js b/meshuser.js index f13c3445..0843cad5 100644 --- a/meshuser.js +++ b/meshuser.js @@ -686,6 +686,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (!r[meshid]) { r[meshid] = []; } delete docs[i].meshid; + // Remove push messaging token if present + if (docs[i].pmt != null) { docs[i].pmt = 1; } + // Remove Intel AMT credential if present if (docs[i].intelamt != null) { if (docs[i].intelamt.pass != null) { docs[i].intelamt.pass = 1; } @@ -4060,7 +4063,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Check if this user has rights on this nodeid if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid 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 ((nodes == null) || (nodes.length == 1)) { + if ((err == null) && (nodes.length == 1)) { if ((parent.GetMeshRights(user, nodes[0].meshid) & MESHRIGHT_REMOTECONTROL) != 0) { // Add a user authentication cookie to a url var cookieContent = { userid: user._id, domainid: user.domain }; @@ -5191,6 +5194,31 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use try { ws.send(JSON.stringify(responseCmd)); } catch (ex) { } break; } + case 'pushmessage': { + // 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.title, 1, 1024) == false) break; // Check title + if (common.validateString(command.msg, 1, 1024) == false) break; // Check message + 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')) { + // 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 + parent.parent.firebase.messaging().sendToDevice(node.pmt, payload, options) + .then(function (response) { + parent.parent.debug('email', 'Successfully send push message to device ' + node.name + ', title: ' + command.title + ', msg: ' + command.msg); + }) + .catch(function (error) { + parent.parent.debug('email', 'Failed to send push message to device ' + node.name + ', title: ' + command.title + ', msg: ' + command.msg + ', error: ' + error); + }); + } + } + }); + break; + } case 'print': { console.log(command.value); break; diff --git a/views/default.handlebars b/views/default.handlebars index f5539791..78a69408 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -5974,7 +5974,7 @@ } x += ''; x += ''; - if ((connectivity & 1) && (meshrights & 8) && (node.agent.id != 14)) { x += ''; } + if ((meshrights & 8) && ((connectivity & 1) || (node.pmt == 1))) { x += ''; } //if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += ''; } if ((connectivity & 1) && (meshrights & 8) && (node.agent.id == 14)) { x += ''; } if ((serverinfo.guestdevicesharing !== false) && (node.agent != null) && (node.agent.caps & 3) && (connectivity & 1) && (meshrights & 0x80008) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 0x1000) == 0))) { x += ''; } @@ -6338,7 +6338,11 @@ } function deviceMessageFunctionEx() { - meshserver.send({ action: 'msg', type: 'messagebox', nodeid: currentNode._id, title: decodeURIComponent('{{{extitle}}}'), msg: Q('d2devMessage').value }); + if (currentNode.pmt == 1) { + meshserver.send({ action: 'pushmessage', nodeid: currentNode._id, title: decodeURIComponent('{{{extitle}}}'), msg: Q('d2devMessage').value }); + } else { + meshserver.send({ action: 'msg', type: 'messagebox', nodeid: currentNode._id, title: decodeURIComponent('{{{extitle}}}'), msg: Q('d2devMessage').value }); + } } function deskClipboardInFunction() { @@ -13752,7 +13756,7 @@ x += '
'; x += '
'; x += '
'; - x += '
'; + x += '
'; x += '
' + "Web Server" + '
'; x += '
'; x += '
'; diff --git a/webserver.js b/webserver.js index 6770f8ba..b3fc28b2 100644 --- a/webserver.js +++ b/webserver.js @@ -6293,11 +6293,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.CloneSafeNode = function (node) { if (typeof node != 'object') { return node; } var r = node; - if ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null))) { + if ((r.pmt != null) || ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null)))) { r = Object.assign({}, r); // Shallow clone - r.intelamt = Object.assign({}, r.intelamt); // Shallow clone - if (r.intelamt.pass != null) { r.intelamt.pass = 1; }; // Remove the Intel AMT administrator password from the node - if (r.intelamt.mpspass != null) { r.intelamt.mpspass = 1; }; // Remove the Intel AMT MPS password from the node + if (r.pmt != null) { r.pmt = 1; } + if ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null))) { + r.intelamt = Object.assign({}, r.intelamt); // Shallow clone + if (r.intelamt.pass != null) { r.intelamt.pass = 1; }; // Remove the Intel AMT administrator password from the node + if (r.intelamt.mpspass != null) { r.intelamt.mpspass = 1; }; // Remove the Intel AMT MPS password from the node + } } return r; }