diff --git a/social/email/61-email.html b/social/email/61-email.html
index 42cccb5f..304be8b5 100644
--- a/social/email/61-email.html
+++ b/social/email/61-email.html
@@ -417,7 +417,7 @@
@@ -478,6 +479,7 @@
keyFile: { value: "" },
mtausers: { value: [] },
auth: { value: false },
+ authType: { value: false },
expert: { value: '{"logger":false}' }
},
inputs: 0,
@@ -491,7 +493,8 @@
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
- let node = this;
+ const node = this;
+
// Certificate settings
$("#node-input-secure").change(secVisibility);
$("#node-input-starttls").change(secVisibility);
@@ -505,6 +508,21 @@
}
}
// User Management
+ const noneType = { value: 'none', label: node._('email.label.noAuth'), hasValue: false }
+ const builtInType = { value: 'built-in', label: node._('email.label.auth'), hasValue: false }
+ const AUTH_TYPES = ['none', 'built-in', "flow", "global", "jsonata"]
+ if (!AUTH_TYPES.includes(node.authType)) {
+ // in-place upgrade
+ node.authType = (node.auth === "true" || node.auth === true) ? "built-in" : "none";
+ $('#node-input-authType').val(node.authType);
+ }
+ const $authTypedInput = $('#node-input-auth').typedInput({
+ default: 'none',
+ typeField: $('#node-input-authType'),
+ types: [noneType, builtInType, 'flow', 'global', 'jsonata']
+ })
+ $authTypedInput.change(builtInUsersVisibility);
+
let cacheItemCount = 0;
if (node.mtausers && node.mtausers.length > 0) {
cacheItemCount = node.mtausers.length;
@@ -524,7 +542,7 @@
id: "node-input-email-users-name" + id,
class: "userName",
type: "text",
- style: "margin-left:5px;width:100px;",
+ style: "margin-left:5px;width:42%;",
placeholder: "name"
}).appendTo(row);
@@ -532,7 +550,7 @@
id: "node-input-email-users-password" + id,
class: "userPassword",
type: "password",
- style: "margin: 0 auto;width:50%;min-width:20px;margin-left:5px",
+ style: "margin: 0 auto;width:42%;margin-left:5px",
placeholder: "password"
}).appendTo(row);
@@ -561,7 +579,24 @@
$("#node-input-email-users-container").append(container);
}
-
+ function builtInUsersVisibility() {
+ const authType = $authTypedInput.typedInput('type');
+ if (authType === 'built-in') {
+ // show user list
+ $("#node-input-email-users-add").show();
+ $("#node-input-email-users-container-div").show();
+ $("#node-custom-auth-tip").hide();
+ } else {
+ $("#node-input-email-users-add").hide();
+ $("#node-input-email-users-container-div").hide();
+ if (authType === 'none') {
+ $("#node-custom-auth-tip").hide();
+ } else {
+ // show user tip for flow/global/jsonata
+ $("#node-custom-auth-tip").show();
+ }
+ }
+ }
$("#node-input-email-users-container").sortable({
axis: "y",
handle: ".node-input-email-users-handle",
@@ -579,20 +614,14 @@
$("#node-input-email-users-container-div").get(0).scrollHeight
);
});
- $("#node-input-auth").change(function () {
- if ($("#node-input-auth").is(":checked")) {
- $("#node-input-email-users-add").show();
- $("#node-input-email-users-container-div").show();
- } else {
- $("#node-input-email-users-add").hide();
- $("#node-input-email-users-container-div").hide();
- }
- });
+
// Expert settings
$("#node-input-expert").typedInput({
type: "json",
types: ["json"]
})
+
+ builtInUsersVisibility();
},
oneditsave: function () {
let node = this;
diff --git a/social/email/61-email.js b/social/email/61-email.js
index 90a52fa0..b423d0a8 100644
--- a/social/email/61-email.js
+++ b/social/email/61-email.js
@@ -631,6 +631,27 @@ module.exports = function(RED) {
this.keyFile = n.keyFile;
this.mtausers = n.mtausers;
this.auth = n.auth;
+ /** @type {"none"|"built-in"|"flow"|"global"|"jsonata"} **/
+ this.authType = n.authType;
+ const AUTH_TYPES = ['none', 'built-in', "flow", "global", "jsonata"]
+ if (!AUTH_TYPES.includes(this.authType)) {
+ // in-place upgrade
+ this.authType = (n.auth === "true" || n.auth === true) ? "built-in" : "none";
+ }
+
+ // basic user list duck type validation
+ const validateUserList = (users) => {
+ if (!Array.isArray(users) || users.length === 0) {
+ return false;
+ }
+ for (const user of users) {
+ if (!user.name || typeof user.password !== "string") {
+ return false;
+ }
+ }
+ return true;
+ }
+
try {
this.options = JSON.parse(n.expert);
} catch (error) {
@@ -650,7 +671,7 @@ module.exports = function(RED) {
if (!node.starttls) {
node.options.disabledCommands.push("STARTTLS");
}
- if (!node.auth) {
+ if (node.authType === "none") {
node.options.disabledCommands.push("AUTH");
}
@@ -686,14 +707,34 @@ module.exports = function(RED) {
});
}
- node.options.onAuth = function (auth, session, callback) {
- let id = node.mtausers.findIndex(function (item) {
- return item.name === auth.username;
- });
- if (id >= 0 && node.mtausers[id].password === auth.password) {
- callback(null, { user: id + 1 });
- } else {
- callback(new Error("Invalid username or password"));
+ node.options.onAuth = async function (auth, session, callback) {
+ try {
+ if (node.authType === "none") {
+ return; // auth not enabled - should not reach here
+ }
+ let userList;
+ if (node.authType === "built-in") {
+ // users defined in UI
+ userList = node.mtausers;
+ } else {
+ // users defined in flow, global, or jsonata
+ userList = await asyncEvaluateNodeProperty(RED, node.auth, node.authType, node, {});
+ }
+
+ if (!validateUserList(userList)) {
+ callback(new Error("Invalid user list"));
+ }
+
+ let id = userList.findIndex(function (item) {
+ return item.name === auth.username;
+ });
+ if (id >= 0 && userList[id].password === auth.password) {
+ callback(null, { user: id + 1 });
+ } else {
+ callback(new Error("Invalid username or password"));
+ }
+ } catch (error) {
+ callback(new Error("Invalid user list"));
}
}
@@ -711,4 +752,16 @@ module.exports = function(RED) {
}
RED.nodes.registerType("e-mail mta",EmailMtaNode);
+ function asyncEvaluateNodeProperty (RED, value, type, node, msg) {
+ return new Promise(function (resolve, reject) {
+ RED.util.evaluateNodeProperty(value, type, node, msg, function (e, r) {
+ if (e) {
+ reject(e)
+ } else {
+ resolve(r)
+ }
+ })
+ })
+ }
+
};
diff --git a/social/email/locales/en-US/61-email.json b/social/email/locales/en-US/61-email.json
index d825cb44..ae53b74c 100644
--- a/social/email/locales/en-US/61-email.json
+++ b/social/email/locales/en-US/61-email.json
@@ -45,6 +45,7 @@
"keyFile":"Private key",
"users": "Users",
"auth": "Authenticate users",
+ "noAuth": "No authentication",
"addButton": "Add",
"expert": "Expert"
},
@@ -52,7 +53,8 @@
"tip": {
"cred": "Note: Copied credentials from global emailkeys.js file.",
"recent": "Tip: Only retrieves the single most recent email.",
- "mta": "Note: To use ports below 1024 you may need elevated (root) privileges. See help sidebar."
+ "mta": "Note: To use ports below 1024 you may need elevated (root) privileges. See help sidebar.",
+ "mta-custom-auth": "Expected Format: [{\"name\":string, \"password\":string}, ...]"
},
"status": {
"messagesent": "Message sent: __response__",
diff --git a/social/email/locales/ja/61-email.json b/social/email/locales/ja/61-email.json
index 82c7bcc7..1cc49aa8 100644
--- a/social/email/locales/ja/61-email.json
+++ b/social/email/locales/ja/61-email.json
@@ -40,7 +40,8 @@
"tip": {
"cred": "注釈: emailkeys.jsファイルから認証情報をコピーしました。",
"recent": "注釈: 最新のメールを1件のみ取得します。",
- "mta": "注釈: 1024未満のポートを使用するには、昇格された(root)特権が必要です。ヘルプサイドバーを参照してください。"
+ "mta": "注釈: 1024未満のポートを使用するには、昇格された(root)特権が必要です。ヘルプサイドバーを参照してください。",
+ "mta-custom-auth": "期待される形式: [{\"name\":string, \"password\":string}, ...]"
},
"status": {
"messagesent": "メッセージを送信しました: __response__",