mirror of
https://github.com/node-red/node-red-nodes.git
synced 2025-12-26 23:16:47 +01:00
Email Node permit runtime users list for auth (#1104)
* Add i18n labels and tips * improve name/pass fields size * layout improvements * typedInput for auth type
This commit is contained in:
committed by
GitHub
parent
4b1f12ae04
commit
93c2754784
@@ -417,7 +417,7 @@
|
||||
<script type="text/html" data-template-name="e-mail mta">
|
||||
<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>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name" style="width: calc(100% - 110px)">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-random"></i> <span data-i18n="email.label.port"></span></label>
|
||||
@@ -443,12 +443,13 @@
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-auth"><i class="fa fa-user"></i> <span data-i18n="email.label.users"></span></label>
|
||||
<input type="checkbox" id="node-input-auth" style="display:inline-block; width:20px; vertical-align:baseline;">
|
||||
<span data-i18n="email.label.auth"></span>
|
||||
<input type="hidden" id="node-input-authType">
|
||||
<input type="input" id="node-input-auth" style="width: calc(100% - 110px)">
|
||||
</div>
|
||||
<div class="form-tips" id="node-custom-auth-tip"><span data-i18n="[html]email.tip.mta-custom-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;">
|
||||
min-height: 200px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll; resize: vertical;">
|
||||
<ol id="node-input-email-users-container" style="list-style-type:none; margin: 0;"></ol>
|
||||
</div>
|
||||
</div>
|
||||
@@ -460,7 +461,7 @@
|
||||
</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">
|
||||
<input type="text" id="node-input-expert" style="width: calc(100% - 110px)">
|
||||
</div>
|
||||
<div class="form-tips" id="node-tip"><span data-i18n="[html]email.tip.mta"></span></div>
|
||||
</script>
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -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": "<b>Note:</b> Copied credentials from global emailkeys.js file.",
|
||||
"recent": "Tip: Only retrieves the single most recent email.",
|
||||
"mta": "<b>Note:</b> To use ports below 1024 you may need elevated (root) privileges. See help sidebar."
|
||||
"mta": "<b>Note:</b> To use ports below 1024 you may need elevated (root) privileges. See help sidebar.",
|
||||
"mta-custom-auth": "<b>Expected Format:</b> <code>[{\"name\":string, \"password\":string}, ...]</code>"
|
||||
},
|
||||
"status": {
|
||||
"messagesent": "Message sent: __response__",
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
"tip": {
|
||||
"cred": "<b>注釈:</b> emailkeys.jsファイルから認証情報をコピーしました。",
|
||||
"recent": "注釈: 最新のメールを1件のみ取得します。",
|
||||
"mta": "<b>注釈:</b> 1024未満のポートを使用するには、昇格された(root)特権が必要です。ヘルプサイドバーを参照してください。"
|
||||
"mta": "<b>注釈:</b> 1024未満のポートを使用するには、昇格された(root)特権が必要です。ヘルプサイドバーを参照してください。",
|
||||
"mta-custom-auth": "<b>期待される形式:</b> <code>[{\"name\":string, \"password\":string}, ...]</code>"
|
||||
},
|
||||
"status": {
|
||||
"messagesent": "メッセージを送信しました: __response__",
|
||||
|
||||
Reference in New Issue
Block a user