meshsms.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /**
  2. * @description MeshCentral SMS gateway communication module
  3. * @author Ylian Saint-Hilaire
  4. * @copyright Intel Corporation 2018-2022
  5. * @license Apache-2.0
  6. * @version v0.0.1
  7. */
  8. /*xjslint node: true */
  9. /*xjslint plusplus: true */
  10. /*xjslint maxlen: 256 */
  11. /*jshint node: true */
  12. /*jshint strict: false */
  13. /*jshint esversion: 6 */
  14. "use strict";
  15. /*
  16. // For Twilio, add this in config.json
  17. "sms": {
  18. "provider": "twilio",
  19. "sid": "ACxxxxxxxxx",
  20. "auth": "xxxxxxx",
  21. "from": "+15555555555"
  22. },
  23. // For Plivo, add this in config.json
  24. "sms": {
  25. "provider": "plivo",
  26. "id": "xxxxxxx",
  27. "token": "xxxxxxx",
  28. "from": "15555555555"
  29. }
  30. // For Telnyx, add this in config.json
  31. "sms": {
  32. "provider": "telnyx",
  33. "apikey": "xxxxxxx",
  34. "from": "15555555555"
  35. }
  36. // For URL, add this in config.json
  37. "sms": {
  38. "provider": "url",
  39. "url": "https://sample.com/?phone={{phone}}&msg={{message}}"
  40. }
  41. */
  42. // Construct a SMS server object
  43. module.exports.CreateMeshSMS = function (parent) {
  44. var obj = {};
  45. obj.parent = parent;
  46. obj.provider = null;
  47. // SMS gateway provider setup
  48. switch (parent.config.sms.provider) {
  49. case 'twilio': {
  50. // Validate Twilio configuration values
  51. if (typeof parent.config.sms.sid != 'string') { console.log('Invalid or missing SMS gateway provider sid.'); return null; }
  52. if (typeof parent.config.sms.auth != 'string') { console.log('Invalid or missing SMS gateway provider auth.'); return null; }
  53. if (typeof parent.config.sms.from != 'string') { console.log('Invalid or missing SMS gateway provider from.'); return null; }
  54. // Setup Twilio
  55. var Twilio = require('twilio');
  56. obj.provider = new Twilio(parent.config.sms.sid, parent.config.sms.auth);
  57. break;
  58. }
  59. case 'plivo': {
  60. // Validate Plivo configuration values
  61. if (typeof parent.config.sms.id != 'string') { console.log('Invalid or missing SMS gateway provider id.'); return null; }
  62. if (typeof parent.config.sms.token != 'string') { console.log('Invalid or missing SMS gateway provider token.'); return null; }
  63. if (typeof parent.config.sms.from != 'string') { console.log('Invalid or missing SMS gateway provider from.'); return null; }
  64. // Setup Plivo
  65. var plivo = require('plivo');
  66. obj.provider = new plivo.Client(parent.config.sms.id, parent.config.sms.token);
  67. break;
  68. }
  69. case 'telnyx': {
  70. // Validate Telnyx configuration values
  71. if (typeof parent.config.sms.apikey != 'string') { console.log('Invalid or missing SMS gateway provider apikey.'); return null; }
  72. if (typeof parent.config.sms.from != 'string') { console.log('Invalid or missing SMS gateway provider from.'); return null; }
  73. // Setup Telnyx
  74. obj.provider = require('telnyx')(parent.config.sms.apikey);
  75. break;
  76. }
  77. case 'url': {
  78. // Validate URL configuration values
  79. if (parent.config.sms.url != 'console') {
  80. if (typeof parent.config.sms.url != 'string') { console.log('Invalid or missing SMS gateway URL value.'); return null; }
  81. if (!parent.config.sms.url.toLowerCase().startsWith('http://') && !parent.config.sms.url.toLowerCase().startsWith('https://')) { console.log('Invalid or missing SMS gateway, URL must start with http:// or https://.'); return null; }
  82. if (parent.config.sms.url.indexOf('{{message}}') == -1) { console.log('Invalid or missing SMS gateway, URL must include {{message}}.'); return null; }
  83. if (parent.config.sms.url.indexOf('{{phone}}') == -1) { console.log('Invalid or missing SMS gateway, URL must include {{phone}}.'); return null; }
  84. }
  85. break;
  86. }
  87. default: {
  88. // Unknown SMS gateway provider
  89. console.log('Unknown SMS gateway provider: ' + parent.config.sms.provider);
  90. return null;
  91. }
  92. }
  93. // Send an SMS message
  94. obj.sendSMS = function (to, msg, func) {
  95. parent.debug('email', 'Sending SMS to: ' + to + ': ' + msg);
  96. if (parent.config.sms.provider == 'twilio') { // Twilio
  97. obj.provider.messages.create({
  98. from: parent.config.sms.from,
  99. to: to,
  100. body: msg
  101. }, function (err, result) {
  102. if (err != null) { parent.debug('email', 'SMS error: ' + err.message); } else { parent.debug('email', 'SMS result: ' + JSON.stringify(result)); }
  103. if (func != null) { func((err == null) && (result.status == 'queued'), err ? err.message : null, result); }
  104. });
  105. } else if (parent.config.sms.provider == 'plivo') { // Plivo
  106. if (to.split('-').join('').split(' ').join('').split('+').join('').length == 10) { to = '1' + to; } // If we only have 10 digits, add a 1 in front.
  107. obj.provider.messages.create(
  108. parent.config.sms.from,
  109. to,
  110. msg
  111. ).then(function (result) {
  112. parent.debug('email', 'SMS result: ' + JSON.stringify(result));
  113. if (func != null) { func((result != null) && (result.messageUuid != null), null, result); }
  114. }
  115. ).catch(function (err) {
  116. var msg = null;
  117. if ((err != null) && err.message) { msg = JSON.parse(err.message).error; }
  118. parent.debug('email', 'SMS error: ' + msg);
  119. if (func != null) { func(false, msg, null); }
  120. }
  121. );
  122. } else if (parent.config.sms.provider == 'telnyx') { // Telnyx
  123. obj.provider.messages.create({
  124. from: parent.config.sms.from,
  125. to: to,
  126. text: msg
  127. }, function (err, result) {
  128. if (err != null) { parent.debug('email', 'SMS error: ' + err.type); } else { parent.debug('email', 'SMS result: ' + JSON.stringify(result)); }
  129. if (func != null) { func((err == null), err ? err.type : null, result); }
  130. });
  131. } else if (parent.config.sms.provider == 'url') { // URL
  132. if (parent.config.sms.url == 'console') {
  133. // This is for debugging, just display the SMS to the console
  134. console.log('SMS (' + to + '): ' + msg);
  135. if (func != null) { func(true, null, null); }
  136. } else {
  137. var sms = parent.config.sms.url.split('{{phone}}').join(encodeURIComponent(to)).split('{{message}}').join(encodeURIComponent(msg));
  138. parent.debug('email', 'SMS URL: ' + sms);
  139. sms = require('url').parse(sms);
  140. if (sms.protocol == 'https:') {
  141. // HTTPS GET request
  142. const options = { hostname: sms.hostname, port: sms.port ? sms.port : 443, path: sms.path, method: 'GET', rejectUnauthorized: false };
  143. const request = require('https').request(options, function (res) { parent.debug('email', 'SMS result: ' + res.statusCode); if (func != null) { func(res.statusCode == 200, (res.statusCode == 200) ? null : res.statusCode, null); } res.on('data', function (d) { }); });
  144. request.on('error', function (err) { parent.debug('email', 'SMS error: ' + err); if (func != null) { func(false, err, null); } });
  145. request.end();
  146. } else {
  147. // HTTP GET request
  148. const options = { hostname: sms.hostname, port: sms.port ? sms.port : 80, path: sms.path, method: 'GET' };
  149. const request = require('http').request(options, function (res) { parent.debug('email', 'SMS result: ' + res.statusCode); if (func != null) { func(res.statusCode == 200, (res.statusCode == 200) ? null : res.statusCode, null); } res.on('data', function (d) { }); });
  150. request.on('error', function (err) { parent.debug('email', 'SMS error: ' + err); if (func != null) { func(false, err, null); } });
  151. request.end();
  152. }
  153. }
  154. }
  155. }
  156. // Get the correct SMS template
  157. function getTemplate(templateNumber, domain, lang) {
  158. parent.debug('email', 'Getting SMS template #' + templateNumber + ', lang: ' + lang);
  159. if (Array.isArray(lang)) { lang = lang[0]; } // TODO: For now, we only use the first language given.
  160. var r = {}, emailsPath = null;
  161. if ((domain != null) && (domain.webemailspath != null)) { emailsPath = domain.webemailspath; }
  162. else if (obj.parent.webEmailsOverridePath != null) { emailsPath = obj.parent.webEmailsOverridePath; }
  163. else if (obj.parent.webEmailsPath != null) { emailsPath = obj.parent.webEmailsPath; }
  164. if ((emailsPath == null) || (obj.parent.fs.existsSync(emailsPath) == false)) { return null }
  165. // Get the non-english email if needed
  166. var txtfile = null;
  167. if ((lang != null) && (lang != 'en')) {
  168. var translationsPath = obj.parent.path.join(emailsPath, 'translations');
  169. var translationsPathTxt = obj.parent.path.join(emailsPath, 'translations', 'sms-messages_' + lang + '.txt');
  170. if (obj.parent.fs.existsSync(translationsPath) && obj.parent.fs.existsSync(translationsPathTxt)) {
  171. txtfile = obj.parent.fs.readFileSync(translationsPathTxt).toString();
  172. }
  173. }
  174. // Get the english email
  175. if (txtfile == null) {
  176. var pathTxt = obj.parent.path.join(emailsPath, 'sms-messages.txt');
  177. if (obj.parent.fs.existsSync(pathTxt)) {
  178. txtfile = obj.parent.fs.readFileSync(pathTxt).toString();
  179. }
  180. }
  181. // No email templates
  182. if (txtfile == null) { return null; }
  183. // Decode the TXT file
  184. var lines = txtfile.split('\r\n').join('\n').split('\n')
  185. if (lines.length <= templateNumber) return null;
  186. return lines[templateNumber];
  187. }
  188. // Send phone number verification SMS
  189. obj.sendPhoneCheck = function (domain, phoneNumber, verificationCode, language, func) {
  190. parent.debug('email', "Sending verification SMS to " + phoneNumber);
  191. var sms = getTemplate(0, domain, language);
  192. if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
  193. // Setup the template
  194. sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
  195. sms = sms.split('[[1]]').join(verificationCode);
  196. // Send the SMS
  197. obj.sendSMS(phoneNumber, sms, func);
  198. };
  199. // Send phone number verification SMS
  200. obj.sendToken = function (domain, phoneNumber, verificationCode, language, func) {
  201. parent.debug('email', "Sending login token SMS to " + phoneNumber);
  202. var sms = getTemplate(1, domain, language);
  203. if (sms == null) { parent.debug('email', "Error: Failed to get SMS template"); return; } // No SMS template found
  204. // Setup the template
  205. sms = sms.split('[[0]]').join(domain.title ? domain.title : 'MeshCentral');
  206. sms = sms.split('[[1]]').join(verificationCode);
  207. // Send the SMS
  208. obj.sendSMS(phoneNumber, sms, func);
  209. };
  210. return obj;
  211. };