From df1689bedbe72efad6a48cb9e58c585a75d6afa7 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 25 Aug 2025 14:56:32 +0100 Subject: [PATCH] typedInput for auth type --- social/email/61-email.html | 57 ++++++++++++++++++++++-------- social/email/61-email.js | 71 +++++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 23 deletions(-) diff --git a/social/email/61-email.html b/social/email/61-email.html index 794919d6..304be8b5 100644 --- a/social/email/61-email.html +++ b/social/email/61-email.html @@ -443,9 +443,10 @@
- - + +
+
@@ -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,9 +508,24 @@ } } // 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.users.length; + cacheItemCount = node.mtausers.length; node.mtausers.forEach(function (element, index, array) { generateUserEntry(element, index); }); @@ -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) + } + }) + }) + } + };