Added native support for SendGrid.

This commit is contained in:
Ylian Saint-Hilaire 2020-12-16 14:55:22 -08:00
parent 92d60dbfbe
commit ddd34f56f5
4 changed files with 84 additions and 37 deletions

View File

@ -545,14 +545,24 @@
},
"required": [ "serverId", "servers" ]
},
"sendgrid": {
"title" : "SendGrid.com Email server",
"description": "Connects MeshCentral to the SendGrid email server, allows MeshCentral to send email messages for 2FA or user notification.",
"type": "object",
"properties": {
"from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
"apikey": { "type": "string", "description": "The SendGrid API key." }
},
"required": [ "from", "apikey" ]
},
"smtp": {
"title" : "Email server",
"description": "Connects MeshCentral to a email server, allows MeshCentral to send email messages for 2FA or user notification.",
"title" : "SMTP email server",
"description": "Connects MeshCentral to a SMTP email server, allows MeshCentral to send email messages for 2FA or user notification.",
"type": "object",
"properties": {
"host": { "type": "string", "format": "hostname" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
"from": { "type": "string", "format": "email" },
"from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
"tls": { "type": "boolean" },
"tlscertcheck": { "type": "boolean" },
"tlsstrict": { "type": "boolean" },

View File

@ -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', '$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', '$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(','); } }
@ -1440,7 +1440,13 @@ function CreateMeshCentralServer(config, args) {
}
// Setup email server
if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
if (obj.config.sendgrid != null) {
// Sendgrid server
obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
obj.mailserver.verify();
if (obj.args.lanonly == true) { addServerWarning("SendGrid server has limited use in LAN mode."); }
} else if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
// SMTP server
obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
obj.mailserver.verify();
if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode."); }
@ -2751,6 +2757,7 @@ function InstallModules(modules, func) {
var moduleInfo = moduleNameAndVersion.split('@', 2);
var moduleName = moduleInfo[0];
var moduleVersion = moduleInfo[1];
if (moduleName == '') { moduleName = moduleNameAndVersion; moduleVersion = undefined; } // If the module name starts with @, don't use @ as a version seperator.
try {
// Does the module need a specific version?
if (moduleVersion) {
@ -2888,6 +2895,7 @@ function mainStart() {
if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent'); } // Required for HTTP/HTTPS proxy support
else if (config.settings.xmongodb != null) { modules.push('mongojs'); } // Add MongoJS, old driver.
if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support
if (config.sendgrid != null) { modules.push('@sendgrid/mail'); } // Add SendGrid support
if (args.translate) { modules.push('jsdom'); modules.push('esprima'); modules.push('minify-js'); modules.push('html-minifier'); } // Translation support
// If running NodeJS < 8, install "util.promisify"

View File

@ -26,19 +26,25 @@ module.exports.CreateMeshMail = function (parent) {
obj.mailCookieEncryptionKey = null;
//obj.mailTemplates = {};
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.
const nodemailer = require('nodemailer');
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; }
//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; }
// Setup mail server
var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { } };
//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 } };
if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; }
if (parent.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
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; }
if ((parent.config.smtp.user != null) && (parent.config.smtp.pass != null)) { options.auth = { user: parent.config.smtp.user, pass: parent.config.smtp.pass }; }
obj.smtpServer = nodemailer.createTransport(options);
if (parent.config.sendgrid != null) {
// Setup SendGrid mail server
obj.sendGridServer = require('@sendgrid/mail');
obj.sendGridServer.setApiKey(parent.config.sendgrid.apikey);
} else if (parent.config.smtp != null) {
// Setup SMTP mail server
const nodemailer = require('nodemailer');
var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: {} };
//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 } };
if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; }
if (parent.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
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; }
if ((parent.config.smtp.user != null) && (parent.config.smtp.pass != null)) { options.auth = { user: parent.config.smtp.user, pass: parent.config.smtp.pass }; }
obj.smtpServer = nodemailer.createTransport(options);
}
// Get the correct mail template object
function getTemplate(name, domain, lang) {
@ -143,7 +149,11 @@ module.exports.CreateMeshMail = function (parent) {
// Send a generic email
obj.sendMail = function (to, subject, text, html) {
obj.pendingMails.push({ to: to, from: parent.config.smtp.from, subject: subject, text: text, html: html });
if (parent.config.sendgrid != null) {
obj.pendingMails.push({ to: to, from: parent.config.sendgrid.from, subject: subject, text: text, html: html });
} else if (parent.config.smtp != null) {
obj.pendingMails.push({ to: to, from: parent.config.smtp.from, subject: subject, text: text, html: html });
}
sendNextMail();
};
@ -300,36 +310,51 @@ module.exports.CreateMeshMail = function (parent) {
var mailToSend = obj.pendingMails[0];
obj.sendingMail = true;
parent.debug('email', 'SMTP sending mail to ' + mailToSend.to + '.');
obj.smtpServer.sendMail(mailToSend, function (err, info) {
parent.debug('email', 'SMTP response: ' + JSON.stringify(err) + ', ' + JSON.stringify(info));
obj.sendingMail = false;
if (err == null) {
// Send the next mail
obj.pendingMails.shift();
obj.retry = 0;
sendNextMail();
} else {
obj.retry++;
parent.debug('email', 'SMTP server failed (Retry:' + obj.retry + '): ' + JSON.stringify(err));
console.log('SMTP server failed (Retry:' + obj.retry + '/3): ' + JSON.stringify(err));
// Wait and try again
if (obj.retry < 3) {
setTimeout(sendNextMail, 10000);
} else {
// Failed, send the next mail
parent.debug('email', 'SMTP server failed (Skipping): ' + JSON.stringify(err));
console.log('SMTP server failed (Skipping): ' + JSON.stringify(err));
if (obj.sendGridServer != null) {
// SendGrid send
parent.debug('email', 'SendGrid sending mail to ' + mailToSend.to + '.');
obj.sendGridServer
.send(mailToSend)
.then(function () {
parent.debug('email', 'SendGrid sending success.');
}, function (error) {
parent.debug('email', 'SendGrid sending error: ' + JSON.stringify(error));
});
} else if (obj.smtpServer != null) {
// SMTP send
parent.debug('email', 'SMTP sending mail to ' + mailToSend.to + '.');
obj.smtpServer.sendMail(mailToSend, function (err, info) {
parent.debug('email', 'SMTP response: ' + JSON.stringify(err) + ', ' + JSON.stringify(info));
obj.sendingMail = false;
if (err == null) {
// Send the next mail
obj.pendingMails.shift();
obj.retry = 0;
sendNextMail();
} else {
obj.retry++;
parent.debug('email', 'SMTP server failed (Retry:' + obj.retry + '): ' + JSON.stringify(err));
console.log('SMTP server failed (Retry:' + obj.retry + '/3): ' + JSON.stringify(err));
// Wait and try again
if (obj.retry < 3) {
setTimeout(sendNextMail, 10000);
} else {
// Failed, send the next mail
parent.debug('email', 'SMTP server failed (Skipping): ' + JSON.stringify(err));
console.log('SMTP server failed (Skipping): ' + JSON.stringify(err));
obj.pendingMails.shift();
obj.retry = 0;
sendNextMail();
}
}
}
});
});
}
}
// Send out the next mail in the pending list
obj.verify = function () {
if (obj.smtpServer == null) return;
obj.smtpServer.verify(function (err, info) {
if (err == null) {
console.log('SMTP mail server ' + parent.config.smtp.host + ' working as expected.');

View File

@ -385,6 +385,10 @@
"__tlsstrict__": "When set to true, TLS cypher setup is more limited, SSLv2 and SSLv3 are not allowed.",
"_tlsstrict": true
},
"_sendgrid": {
"from": "myemail@myserver.com",
"apikey": "***********"
},
"_sms": {
"provider": "twilio",
"sid": "ACxxxxxxxxx",