Added native support for SendGrid.

Ylian Saint-Hilaire committed Dec 16, 2020 at 14:55 UTC ddd34f56f5eee18120cad6064f7d6cd609a4bc1f
4 files changed +84 -37
meshcentral-config-schema.json
+13 -3
@@ -545,14 +545,24 @@
545 },
546 "required": [ "serverId", "servers" ]
547 },
548 + "sendgrid": {
549 + "title" : "SendGrid.com Email server",
550 + "description": "Connects MeshCentral to the SendGrid email server, allows MeshCentral to send email messages for 2FA or user notification.",
551 + "type": "object",
552 + "properties": {
553 + "from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
554 + "apikey": { "type": "string", "description": "The SendGrid API key." }
555 + },
556 + "required": [ "from", "apikey" ]
557 + },
558 "smtp": {
549 - "title" : "Email server",
550 - "description": "Connects MeshCentral to a email server, allows MeshCentral to send email messages for 2FA or user notification.",
559 + "title" : "SMTP email server",
560 + "description": "Connects MeshCentral to a SMTP email server, allows MeshCentral to send email messages for 2FA or user notification.",
561 "type": "object",
562 "properties": {
563 "host": { "type": "string", "format": "hostname" },
564 "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
555 - "from": { "type": "string", "format": "email" },
565 + "from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
566 "tls": { "type": "boolean" },
567 "tlscertcheck": { "type": "boolean" },
568 "tlsstrict": { "type": "boolean" },
meshcentral.js
+10 -2
@@ -678,7 +678,7 @@ function CreateMeshCentralServer(config, args) {
678 }
679
680 // Check top level configuration for any unreconized values
681 - if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".'); } } }
681 + 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 + '\".'); } } }
682
683 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(','); } }
684 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(','); } }
@@ -1440,7 +1440,13 @@ function CreateMeshCentralServer(config, args) {
1440 }
1441
1442 // Setup email server
1443 - if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
1443 + if (obj.config.sendgrid != null) {
1444 + // Sendgrid server
1445 + obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
1446 + obj.mailserver.verify();
1447 + if (obj.args.lanonly == true) { addServerWarning("SendGrid server has limited use in LAN mode."); }
1448 + } else if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
1449 + // SMTP server
1450 obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
1451 obj.mailserver.verify();
1452 if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode."); }
@@ -2751,6 +2757,7 @@ function InstallModules(modules, func) {
2757 var moduleInfo = moduleNameAndVersion.split('@', 2);
2758 var moduleName = moduleInfo[0];
2759 var moduleVersion = moduleInfo[1];
2760 + if (moduleName == '') { moduleName = moduleNameAndVersion; moduleVersion = undefined; } // If the module name starts with @, don't use @ as a version seperator.
2761 try {
2762 // Does the module need a specific version?
2763 if (moduleVersion) {
@@ -2888,6 +2895,7 @@ function mainStart() {
2895 if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent'); } // Required for HTTP/HTTPS proxy support
2896 else if (config.settings.xmongodb != null) { modules.push('mongojs'); } // Add MongoJS, old driver.
2897 if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support
2898 + if (config.sendgrid != null) { modules.push('@sendgrid/mail'); } // Add SendGrid support
2899 if (args.translate) { modules.push('jsdom'); modules.push('esprima'); modules.push('minify-js'); modules.push('html-minifier'); } // Translation support
2900
2901 // If running NodeJS < 8, install "util.promisify"
meshmail.js
+57 -32
@@ -26,19 +26,25 @@ module.exports.CreateMeshMail = function (parent) {
26 obj.mailCookieEncryptionKey = null;
27 //obj.mailTemplates = {};
28 const constants = (obj.parent.crypto.constants ? obj.parent.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
29 - const nodemailer = require('nodemailer');
29
30 function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
31 //function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
32
34 - // Setup mail server
35 - var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { } };
36 - //var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { secureProtocol: 'SSLv23_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false } };
37 - if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; }
38 - if (parent.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
39 - if (parent.config.smtp.tlsstrict === true) { options.tls.secureProtocol = 'SSLv23_method'; options.tls.ciphers = 'RSA+AES:!aNULL:!MD5:!DSS'; options.tls.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE; }
40 - if ((parent.config.smtp.user != null) && (parent.config.smtp.pass != null)) { options.auth = { user: parent.config.smtp.user, pass: parent.config.smtp.pass }; }
41 - obj.smtpServer = nodemailer.createTransport(options);
33 + if (parent.config.sendgrid != null) {
34 + // Setup SendGrid mail server
35 + obj.sendGridServer = require('@sendgrid/mail');
36 + obj.sendGridServer.setApiKey(parent.config.sendgrid.apikey);
37 + } else if (parent.config.smtp != null) {
38 + // Setup SMTP mail server
39 + const nodemailer = require('nodemailer');
40 + var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: {} };
41 + //var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { secureProtocol: 'SSLv23_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false } };
42 + if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; }
43 + if (parent.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
44 + if (parent.config.smtp.tlsstrict === true) { options.tls.secureProtocol = 'SSLv23_method'; options.tls.ciphers = 'RSA+AES:!aNULL:!MD5:!DSS'; options.tls.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE; }
45 + if ((parent.config.smtp.user != null) && (parent.config.smtp.pass != null)) { options.auth = { user: parent.config.smtp.user, pass: parent.config.smtp.pass }; }
46 + obj.smtpServer = nodemailer.createTransport(options);
47 + }
48
49 // Get the correct mail template object
50 function getTemplate(name, domain, lang) {
@@ -143,7 +149,11 @@ module.exports.CreateMeshMail = function (parent) {
149
150 // Send a generic email
151 obj.sendMail = function (to, subject, text, html) {
146 - obj.pendingMails.push({ to: to, from: parent.config.smtp.from, subject: subject, text: text, html: html });
152 + if (parent.config.sendgrid != null) {
153 + obj.pendingMails.push({ to: to, from: parent.config.sendgrid.from, subject: subject, text: text, html: html });
154 + } else if (parent.config.smtp != null) {
155 + obj.pendingMails.push({ to: to, from: parent.config.smtp.from, subject: subject, text: text, html: html });
156 + }
157 sendNextMail();
158 };
159
@@ -300,36 +310,51 @@ module.exports.CreateMeshMail = function (parent) {
310
311 var mailToSend = obj.pendingMails[0];
312 obj.sendingMail = true;
303 - parent.debug('email', 'SMTP sending mail to ' + mailToSend.to + '.');
304 - obj.smtpServer.sendMail(mailToSend, function (err, info) {
305 - parent.debug('email', 'SMTP response: ' + JSON.stringify(err) + ', ' + JSON.stringify(info));
306 - obj.sendingMail = false;
307 - if (err == null) {
308 - // Send the next mail
309 - obj.pendingMails.shift();
310 - obj.retry = 0;
311 - sendNextMail();
312 - } else {
313 - obj.retry++;
314 - parent.debug('email', 'SMTP server failed (Retry:' + obj.retry + '): ' + JSON.stringify(err));
315 - console.log('SMTP server failed (Retry:' + obj.retry + '/3): ' + JSON.stringify(err));
316 - // Wait and try again
317 - if (obj.retry < 3) {
318 - setTimeout(sendNextMail, 10000);
319 - } else {
320 - // Failed, send the next mail
321 - parent.debug('email', 'SMTP server failed (Skipping): ' + JSON.stringify(err));
322 - console.log('SMTP server failed (Skipping): ' + JSON.stringify(err));
313 +
314 + if (obj.sendGridServer != null) {
315 + // SendGrid send
316 + parent.debug('email', 'SendGrid sending mail to ' + mailToSend.to + '.');
317 + obj.sendGridServer
318 + .send(mailToSend)
319 + .then(function () {
320 + parent.debug('email', 'SendGrid sending success.');
321 + }, function (error) {
322 + parent.debug('email', 'SendGrid sending error: ' + JSON.stringify(error));
323 + });
324 + } else if (obj.smtpServer != null) {
325 + // SMTP send
326 + parent.debug('email', 'SMTP sending mail to ' + mailToSend.to + '.');
327 + obj.smtpServer.sendMail(mailToSend, function (err, info) {
328 + parent.debug('email', 'SMTP response: ' + JSON.stringify(err) + ', ' + JSON.stringify(info));
329 + obj.sendingMail = false;
330 + if (err == null) {
331 + // Send the next mail
332 obj.pendingMails.shift();
333 obj.retry = 0;
334 sendNextMail();
335 + } else {
336 + obj.retry++;
337 + parent.debug('email', 'SMTP server failed (Retry:' + obj.retry + '): ' + JSON.stringify(err));
338 + console.log('SMTP server failed (Retry:' + obj.retry + '/3): ' + JSON.stringify(err));
339 + // Wait and try again
340 + if (obj.retry < 3) {
341 + setTimeout(sendNextMail, 10000);
342 + } else {
343 + // Failed, send the next mail
344 + parent.debug('email', 'SMTP server failed (Skipping): ' + JSON.stringify(err));
345 + console.log('SMTP server failed (Skipping): ' + JSON.stringify(err));
346 + obj.pendingMails.shift();
347 + obj.retry = 0;
348 + sendNextMail();
349 + }
350 }
327 - }
328 - });
351 + });
352 + }
353 }
354
355 // Send out the next mail in the pending list
356 obj.verify = function () {
357 + if (obj.smtpServer == null) return;
358 obj.smtpServer.verify(function (err, info) {
359 if (err == null) {
360 console.log('SMTP mail server ' + parent.config.smtp.host + ' working as expected.');
sample-config-advanced.json
+4
@@ -385,6 +385,10 @@
385 "__tlsstrict__": "When set to true, TLS cypher setup is more limited, SSLv2 and SSLv3 are not allowed.",
386 "_tlsstrict": true
387 },
388 + "_sendgrid": {
389 + "from": "myemail@myserver.com",
390 + "apikey": "***********"
391 + },
392 "_sms": {
393 "provider": "twilio",
394 "sid": "ACxxxxxxxxx",