mirror of
https://github.com/node-red/node-red-nodes.git
synced 2023-10-10 13:36:58 +02:00
Smtp server (#923)
* Email Mta Node added security and authentication * Documentation updated * Original formatting restored
This commit is contained in:
parent
dcfd055860
commit
0fa0816506
@ -306,7 +306,47 @@
|
|||||||
<script type="text/html" data-template-name="e-mail mta">
|
<script type="text/html" data-template-name="e-mail mta">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-port"><i class="fa fa-random"></i> <span data-i18n="email.label.port"></span></label>
|
<label for="node-input-port"><i class="fa fa-random"></i> <span data-i18n="email.label.port"></span></label>
|
||||||
<input type="text" id="node-input-port" style="width:70%;"/>
|
<input type="text" id="node-input-port" style="width:100px">
|
||||||
|
<label style="width:40px"> </label>
|
||||||
|
<input type="checkbox" id="node-input-secure" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||||
|
<span data-i18n="email.label.enableSecure"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-starttls"><i class="fa fa-lock"></i> <span data-i18n="email.label.enableStarttls"></span></label>
|
||||||
|
<input type="checkbox" id="node-input-starttls" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||||
|
<span data-i18n="email.label.starttlsUpgrade"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-certFile"><i class="fa fa-file"></i>
|
||||||
|
<span data-i18n="email.label.certFile"></span></label>
|
||||||
|
<input type="text" id="node-input-certFile" placeholder="server.crt" style="width:100%">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-keyFile"><i class="fa fa-key"></i>
|
||||||
|
<span data-i18n="email.label.keyFile"></span></label>
|
||||||
|
<input type="text" id="node-input-keyFile" placeholder="private.key" style="width:100%">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-auth"><i class="fa fa-user"></i> <span data-i18n="email.label.users"></span></label>
|
||||||
|
<label style="width:144px"> </label>
|
||||||
|
<input type="checkbox" id="node-input-auth" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||||
|
<span data-i18n="email.label.auth"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-row node-input-email-users-container-row" style="margin-bottom: 0px;">
|
||||||
|
<div id="node-input-email-users-container-div" style="box-sizing: border-box; border-radius: 5px;
|
||||||
|
height: 200px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
|
||||||
|
<ol id="node-input-email-users-container" style="list-style-type:none; margin: 0;"></ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<a href="#" class="editor-button editor-button-small" id="node-input-email-users-add" style="margin-top: 4px;">
|
||||||
|
<i class="fa fa-plus"></i>
|
||||||
|
<span data-i18n="email.label.addButton"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-expert"><i class="fa fa-cog"></i> <span data-i18n="email.label.expert"></span></label>
|
||||||
|
<input type="text" id="node-input-expert">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
||||||
@ -321,7 +361,14 @@
|
|||||||
color: "#c7e9c0",
|
color: "#c7e9c0",
|
||||||
defaults: {
|
defaults: {
|
||||||
name: { value: "" },
|
name: { value: "" },
|
||||||
port: {value:"1025",required:true},
|
port: { value: "1025", required: true, validate: RED.validators.number() },
|
||||||
|
secure: { value: false },
|
||||||
|
starttls: { value: false },
|
||||||
|
certFile: { value: "" },
|
||||||
|
keyFile: { value: "" },
|
||||||
|
users: { value: [] },
|
||||||
|
auth: { value: false },
|
||||||
|
expert: { value: '{"logger":false}' }
|
||||||
},
|
},
|
||||||
inputs: 0,
|
inputs: 0,
|
||||||
outputs: 1,
|
outputs: 1,
|
||||||
@ -332,6 +379,104 @@
|
|||||||
},
|
},
|
||||||
labelStyle: function () {
|
labelStyle: function () {
|
||||||
return this.name ? "node_label_italic" : "";
|
return this.name ? "node_label_italic" : "";
|
||||||
|
},
|
||||||
|
oneditprepare: function () {
|
||||||
|
let node = this;
|
||||||
|
// Expert settings
|
||||||
|
$("#node-input-expert").typedInput({
|
||||||
|
type: "json",
|
||||||
|
types: ["json"]
|
||||||
|
})
|
||||||
|
// User Management
|
||||||
|
let cacheItemCount = 0;
|
||||||
|
if (node.users && node.users.length > 0) {
|
||||||
|
cacheItemCount = node.users.length;
|
||||||
|
node.users.forEach(function (element, index, array) {
|
||||||
|
generateUserEntry(element, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function generateUserEntry(user, id) {
|
||||||
|
let container = $("<li/>", {
|
||||||
|
style: "background: #fefefe; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"
|
||||||
|
});
|
||||||
|
let row = $('<div id="row' + id + '"/>').appendTo(container);
|
||||||
|
|
||||||
|
$('<i style="color: #eee; cursor: move;" class="node-input-email-users-handle fa fa-bars"></i>').appendTo(row);
|
||||||
|
|
||||||
|
let userField = $("<input/>", {
|
||||||
|
id: "node-input-email-users-name" + id,
|
||||||
|
class: "userName",
|
||||||
|
type: "text",
|
||||||
|
style: "margin-left:5px;width:100px;",
|
||||||
|
placeholder: "name"
|
||||||
|
}).appendTo(row);
|
||||||
|
|
||||||
|
let passwordField = $("<input/>", {
|
||||||
|
id: "node-input-email-users-password" + id,
|
||||||
|
class: "userPassword",
|
||||||
|
type: "password",
|
||||||
|
style: "margin: 0 auto;width:50%;min-width:20px;margin-left:5px",
|
||||||
|
placeholder: "password"
|
||||||
|
}).appendTo(row);
|
||||||
|
|
||||||
|
userField.val(user.name);
|
||||||
|
passwordField.val(user.password);
|
||||||
|
|
||||||
|
let finalspan = $("<span/>", {
|
||||||
|
style: "float: right;margin-right: 10px;"
|
||||||
|
}).appendTo(row);
|
||||||
|
|
||||||
|
let removeUserButton = $("<a/>", {
|
||||||
|
href: "#",
|
||||||
|
id: "node-button-user-remove" + id,
|
||||||
|
class: "editor-button editor-button-small",
|
||||||
|
style: "margin-top: 7px; margin-left: 5px;"
|
||||||
|
}).appendTo(finalspan);
|
||||||
|
|
||||||
|
$("<i/>", { class: "fa fa-remove" }).appendTo(removeUserButton);
|
||||||
|
|
||||||
|
removeUserButton.click(function () {
|
||||||
|
container.css({ background: "#fee" });
|
||||||
|
container.fadeOut(300, function () {
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#node-input-email-users-container").append(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#node-input-email-users-container").sortable({
|
||||||
|
axis: "y",
|
||||||
|
handle: ".node-input-email-users-handle",
|
||||||
|
cursor: "move"
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#node-input-email-users-container .node-input-email-users-handle").disableSelection();
|
||||||
|
|
||||||
|
$("#node-input-email-users-add").click(function () {
|
||||||
|
if (!cacheItemCount || cacheItemCount < 0) {
|
||||||
|
cacheItemCount = 0;
|
||||||
|
}
|
||||||
|
generateUserEntry({ name: "", password: "" }, cacheItemCount++);
|
||||||
|
$("#node-input-email-users-container-div").scrollTop(
|
||||||
|
$("#node-input-email-users-container-div").get(0).scrollHeight
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function () {
|
||||||
|
let node = this;
|
||||||
|
let cacheUsers = $("#node-input-email-users-container").children();
|
||||||
|
node.users = [];
|
||||||
|
cacheUsers.each(function () {
|
||||||
|
node.users.push({
|
||||||
|
name: $(this)
|
||||||
|
.find(".userName")
|
||||||
|
.val(),
|
||||||
|
password: $(this)
|
||||||
|
.find(".userPassword")
|
||||||
|
.val()
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
@ -19,6 +19,7 @@ module.exports = function(RED) {
|
|||||||
var simpleParser = require("mailparser").simpleParser;
|
var simpleParser = require("mailparser").simpleParser;
|
||||||
var SMTPServer = require("smtp-server").SMTPServer;
|
var SMTPServer = require("smtp-server").SMTPServer;
|
||||||
//var microMTA = require("micromta").microMTA;
|
//var microMTA = require("micromta").microMTA;
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) {
|
if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) {
|
||||||
throw "Error : Requires nodejs version >= 8.";
|
throw "Error : Requires nodejs version >= 8.";
|
||||||
@ -567,14 +568,36 @@ module.exports = function(RED) {
|
|||||||
function EmailMtaNode(n) {
|
function EmailMtaNode(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
this.port = n.port;
|
this.port = n.port;
|
||||||
|
this.secure = n.secure;
|
||||||
|
this.starttls = n.starttls;
|
||||||
|
this.certFile = n.certFile;
|
||||||
|
this.keyFile = n.keyFile;
|
||||||
|
this.users = n.users;
|
||||||
|
this.auth = n.auth;
|
||||||
|
try {
|
||||||
|
this.options = JSON.parse(n.expert);
|
||||||
|
} catch (error) {
|
||||||
|
this.options = {};
|
||||||
|
}
|
||||||
var node = this;
|
var node = this;
|
||||||
|
if (!Array.isArray(node.options.disabledCommands)) {
|
||||||
|
node.options.disabledCommands = [];
|
||||||
|
}
|
||||||
|
node.options.secure = node.secure;
|
||||||
|
if (node.certFile) {
|
||||||
|
node.options.cert = fs.readFileSync(node.certFile);
|
||||||
|
}
|
||||||
|
if (node.keyFile) {
|
||||||
|
node.options.key = fs.readFileSync(node.keyFile);
|
||||||
|
}
|
||||||
|
if (!node.starttls) {
|
||||||
|
node.options.disabledCommands.push("STARTTLS");
|
||||||
|
}
|
||||||
|
if (!node.auth) {
|
||||||
|
node.options.disabledCommands.push("AUTH");
|
||||||
|
}
|
||||||
|
|
||||||
node.mta = new SMTPServer({
|
node.options.onData = function (stream, session, callback) {
|
||||||
secure: false,
|
|
||||||
logger: false,
|
|
||||||
disabledCommands: ['AUTH', 'STARTTLS'],
|
|
||||||
|
|
||||||
onData: function (stream, session, callback) {
|
|
||||||
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
|
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
|
||||||
if (err) { node.error(RED._("email.errors.parsefail"),err); }
|
if (err) { node.error(RED._("email.errors.parsefail"),err); }
|
||||||
else {
|
else {
|
||||||
@ -605,7 +628,19 @@ module.exports = function(RED) {
|
|||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.options.onAuth = function (auth, session, callback) {
|
||||||
|
let id = node.users.findIndex(function (item) {
|
||||||
|
return item.name === auth.username;
|
||||||
});
|
});
|
||||||
|
if (id >= 0 && node.users[id].password === auth.password) {
|
||||||
|
callback(null, { user: id + 1 });
|
||||||
|
} else {
|
||||||
|
callback(new Error("Invalid username or password"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.mta = new SMTPServer(node.options);
|
||||||
|
|
||||||
node.mta.listen(node.port);
|
node.mta.listen(node.port);
|
||||||
|
|
||||||
|
@ -58,11 +58,19 @@
|
|||||||
|
|
||||||
<script type="text/html" data-help-name="e-mail mta">
|
<script type="text/html" data-help-name="e-mail mta">
|
||||||
<p>Mail Transfer Agent - listens on a port for incoming SMTP mails.</p>
|
<p>Mail Transfer Agent - listens on a port for incoming SMTP mails.</p>
|
||||||
<p><b>Note</b>: "NOT for production use" as there is no security built in.
|
<p><b>Note</b>: Default configuration is "NOT for production use" as security is not enabled.
|
||||||
This is primarily for local testing of outbound mail sending, but could be used
|
This is primarily for local testing of outbound mail sending, but could be used
|
||||||
as a mail forwarder to a real email service if required.</p>
|
as a mail forwarder to a real email service if required.</p>
|
||||||
<p>To use ports below 1024, for example 25 or 465, you may need to get privileged access.
|
<p>To use ports below 1024, for example 25 or 465, you may need to get privileged access.
|
||||||
On linux systems this can be done by running
|
On linux systems this can be done by running
|
||||||
<pre>sudo setcap 'cap_net_bind_service=+eip' $(which node)</pre>
|
<pre>sudo setcap 'cap_net_bind_service=+eip' $(which node)</pre>
|
||||||
and restarting Node-RED. Be aware - this gives all node applications access to all ports.</p>
|
and restarting Node-RED. Be aware - this gives all node applications access to all ports.</p>
|
||||||
|
<h3>Security</h3>
|
||||||
|
<p>When <i>Secure connection</i> is checked, the connection will use TLS.
|
||||||
|
If not it is still possible to upgrade clear text socket to TLS socket by checking <i>Start TLS</i>.
|
||||||
|
If you do no specify your own certificate (path to file) then a pregenerated self-signed certificate is used. Any respectful client refuses to accept such certificate.</p>
|
||||||
|
<h3>Authentication</h3>
|
||||||
|
<p>Authentication can be enabled (PLAIN or LOGIN). Add at least one user.</p>
|
||||||
|
<h3>Expert</h3>
|
||||||
|
<p>All options as described in <a href="https://nodemailer.com/extras/smtp-server/" target="_new">nodemailer SMTP server</a> can be made here.</p>
|
||||||
</script>
|
</script>
|
||||||
|
@ -34,7 +34,16 @@
|
|||||||
"never": "never",
|
"never": "never",
|
||||||
"required": "if required",
|
"required": "if required",
|
||||||
"always": "always",
|
"always": "always",
|
||||||
"rejectUnauthorised": "Check server certificate is valid"
|
"rejectUnauthorised": "Check server certificate is valid",
|
||||||
|
"enableSecure": "Secure connection",
|
||||||
|
"enableStarttls": "Start TLS",
|
||||||
|
"starttlsUpgrade": "Upgrade cleartext connection with STARTTLS",
|
||||||
|
"certFile": "Certificate",
|
||||||
|
"keyFile":"Private key",
|
||||||
|
"users": "Users",
|
||||||
|
"auth": "Authenticate users",
|
||||||
|
"addButton": "Add",
|
||||||
|
"expert": "Expert"
|
||||||
},
|
},
|
||||||
"default-message": "__description__\n\nFile from Node-RED is attached: __filename__",
|
"default-message": "__description__\n\nFile from Node-RED is attached: __filename__",
|
||||||
"tip": {
|
"tip": {
|
||||||
|
Loading…
Reference in New Issue
Block a user