1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

REST API for Credentials

Adding RED.nodes.registerCredentials to register the credentials definition server sided.
Adding the property credentials in the template definition client-side.

Connecting the editor to the credential API.
I added a TODO for the validation of Credentials field.
As the other field, the developer should be able to set the credentials as required and also give a validation function.
This commit is contained in:
Antoine Aflalo 2014-05-07 21:19:08 +03:00
parent 848e5a9824
commit 6a7b3cf62c
4 changed files with 184 additions and 64 deletions

View File

@ -15,9 +15,9 @@
**/ **/
RED.editor = function() { RED.editor = function() {
var editing_node = null; var editing_node = null;
// TODO: should IMPORT/EXPORT get their own dialogs? // TODO: should IMPORT/EXPORT get their own dialogs?
function validateNode(node) { function validateNode(node) {
var oldValue = node.valid; var oldValue = node.valid;
node.valid = true; node.valid = true;
@ -31,6 +31,29 @@ RED.editor = function() {
} }
} }
function getCredentialsURL(nodeType, nodeID) {
var dashedType = nodeType.replace(/\s+/g, '-');
return 'credentials/' + dashedType + "/" + nodeID;
}
function sendCredentials(node,credDefinition,prefix) {
var credentials = {};
for (var cred in credDefinition) {
var input = $("#" + prefix + '-' + cred);
var value = input.val();
if (credDefinition[cred].type == 'password' && value == '__PWRD__') {
continue;
}
credentials[cred] = value;
}
$.ajax({
url: getCredentialsURL(node.type, node.id),
type: 'POST',
data: credentials,
success: function (result) {
}
});
}
function validateNodeProperty(node,property,value) { function validateNodeProperty(node,property,value) {
var valid = true; var valid = true;
if ("required" in node._def.defaults[property] && node._def.defaults[property].required) { if ("required" in node._def.defaults[property] && node._def.defaults[property].required) {
@ -49,7 +72,7 @@ RED.editor = function() {
} }
return valid; return valid;
} }
function updateNodeProperties(node) { function updateNodeProperties(node) {
node.resize = true; node.resize = true;
node.dirty = true; node.dirty = true;
@ -74,9 +97,9 @@ RED.editor = function() {
} }
return removedLinks; return removedLinks;
} }
$( "#dialog" ).dialog({ $( "#dialog" ).dialog({
modal: true, modal: true,
autoOpen: false, autoOpen: false,
@ -90,8 +113,8 @@ RED.editor = function() {
var changes = {}; var changes = {};
var changed = false; var changed = false;
var wasDirty = RED.view.dirty(); var wasDirty = RED.view.dirty();
if (editing_node._def.oneditsave) { if (editing_node._def.oneditsave) {
var oldValues = {}; var oldValues = {};
for (var d in editing_node._def.defaults) { for (var d in editing_node._def.defaults) {
@ -105,7 +128,7 @@ RED.editor = function() {
if (rc === true) { if (rc === true) {
changed = true; changed = true;
} }
for (var d in editing_node._def.defaults) { for (var d in editing_node._def.defaults) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") { if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) { if (oldValues[d] !== editing_node[d]) {
@ -119,10 +142,10 @@ RED.editor = function() {
} }
} }
} }
} }
if (editing_node._def.defaults) { if (editing_node._def.defaults) {
for (var d in editing_node._def.defaults) { for (var d in editing_node._def.defaults) {
var input = $("#node-input-"+d); var input = $("#node-input-"+d);
@ -149,7 +172,7 @@ RED.editor = function() {
configNode.users.push(editing_node); configNode.users.push(editing_node);
} }
} }
changes[d] = editing_node[d]; changes[d] = editing_node[d];
editing_node[d] = newValue; editing_node[d] = newValue;
changed = true; changed = true;
@ -158,14 +181,12 @@ RED.editor = function() {
} }
} }
if (editing_node._def.credentials) { if (editing_node._def.credentials) {
// TODO: credentials var prefix = 'node-input';
// 1. call $.ajax/POST 'credentials/'+editing_node.type+"/"+editing_node.id var credDefinition = editing_node._def.credentials;
// with the new values - only pass back password fields sendCredentials(editing_node,credDefinition,prefix);
// that do not equal '__PWRD__'
// See 10-mqtt.html:150 for example code
} }
var removedLinks = updateNodeProperties(editing_node); var removedLinks = updateNodeProperties(editing_node);
if (changed) { if (changed) {
var wasChanged = editing_node.changed; var wasChanged = editing_node.changed;
@ -230,7 +251,7 @@ RED.editor = function() {
editing_node = null; editing_node = null;
} }
}); });
/** /**
* Create a config-node select box for this property * Create a config-node select box for this property
* @param node - the node being edited * @param node - the node being edited
@ -260,7 +281,7 @@ RED.editor = function() {
} }
input.val(label); input.val(label);
} }
/** /**
* Populate the editor dialog input field for this property * Populate the editor dialog input field for this property
* @param node - the node being edited * @param node - the node being edited
@ -279,7 +300,7 @@ RED.editor = function() {
input.val(val); input.val(val);
} }
} }
/** /**
* Add an on-change handler to revalidate a node field * Add an on-change handler to revalidate a node field
* @param node - the node being edited * @param node - the node being edited
@ -295,7 +316,7 @@ RED.editor = function() {
} }
}); });
} }
/** /**
* Prepare all of the editor dialog fields * Prepare all of the editor dialog fields
* @param node - the node being edited * @param node - the node being edited
@ -312,11 +333,22 @@ RED.editor = function() {
attachPropertyChangeHandler(node,d,prefix); attachPropertyChangeHandler(node,d,prefix);
} }
if (definition.credentials) { if (definition.credentials) {
// TODO: credentials // TODO: Validate credentials fields
// 1. call $.getJSON("credentials/"+node.type+"/"+node.id)
// 2. with the response, foreach definition.credentials: $.getJSON(getCredentialsURL(node.type, node.id), function (data) {
// 1. if response.X exists, set prefix-X input field to response.X for (var cred in definition.credentials) {
// 2. if response.hasX exists, set prefix-X password field to '__PWRD__' if (definition.credentials[cred].type == 'password') {
if (data['has' + cred]) {
$('#' + prefix + '-' + cred).val('__PWRD__');
}
else {
$('#' + prefix + '-' + cred).val('');
}
} else {
preparePropertyEditor(data, cred, prefix);
}
}
});
} }
if (definition.oneditprepare) { if (definition.oneditprepare) {
definition.oneditprepare.call(node); definition.oneditprepare.call(node);
@ -325,7 +357,7 @@ RED.editor = function() {
$("#"+prefix+"-"+d).change(); $("#"+prefix+"-"+d).change();
} }
} }
function showEditDialog(node) { function showEditDialog(node) {
editing_node = node; editing_node = node;
RED.view.state(RED.state.EDITING); RED.view.state(RED.state.EDITING);
@ -333,11 +365,11 @@ RED.editor = function() {
prepareEditDialog(node,node._def,"node-input"); prepareEditDialog(node,node._def,"node-input");
$( "#dialog" ).dialog("option","title","Edit "+node.type+" node").dialog( "open" ); $( "#dialog" ).dialog("option","title","Edit "+node.type+" node").dialog( "open" );
} }
function showEditConfigNodeDialog(name,type,id) { function showEditConfigNodeDialog(name,type,id) {
var adding = (id == "_ADD_"); var adding = (id == "_ADD_");
var node_def = RED.nodes.getType(type); var node_def = RED.nodes.getType(type);
var configNode = RED.nodes.node(id); var configNode = RED.nodes.node(id);
if (configNode == null) { if (configNode == null) {
configNode = { configNode = {
@ -349,7 +381,7 @@ RED.editor = function() {
$("#dialog-config-form").html($("script[data-template-name='"+type+"']").html()); $("#dialog-config-form").html($("script[data-template-name='"+type+"']").html());
prepareEditDialog(configNode,node_def,"node-config-input"); prepareEditDialog(configNode,node_def,"node-config-input");
var buttons = $( "#node-config-dialog" ).dialog("option","buttons"); var buttons = $( "#node-config-dialog" ).dialog("option","buttons");
if (adding) { if (adding) {
if (buttons.length == 3) { if (buttons.length == 3) {
@ -368,10 +400,14 @@ RED.editor = function() {
var configType = $(this).dialog('option','node-type'); var configType = $(this).dialog('option','node-type');
var configNode = RED.nodes.node(configId); var configNode = RED.nodes.node(configId);
var configTypeDef = RED.nodes.getType(configType); var configTypeDef = RED.nodes.getType(configType);
if (configTypeDef.credentials) { if (configTypeDef.credentials) {
// CREDENTIAL TODO $.ajax({
// 1. call $.ajax/DELETE "credentials/"+configType+"/"+configId url: getCredentialsURL(configType, configId),
type: 'DELETE',
success: function (result) {
}
});
} }
if (configTypeDef.ondelete) { if (configTypeDef.ondelete) {
configTypeDef.ondelete.call(RED.nodes.node(configId)); configTypeDef.ondelete.call(RED.nodes.node(configId));
@ -397,7 +433,7 @@ RED.editor = function() {
$("#node-config-dialog-user-count").html(configNode.users.length+" node"+(configNode.users.length==1?" uses":"s use")+" this config").show(); $("#node-config-dialog-user-count").html(configNode.users.length+" node"+(configNode.users.length==1?" uses":"s use")+" this config").show();
} }
$( "#node-config-dialog" ).dialog("option","buttons",buttons); $( "#node-config-dialog" ).dialog("option","buttons",buttons);
$( "#node-config-dialog" ) $( "#node-config-dialog" )
.dialog("option","node-adding",adding) .dialog("option","node-adding",adding)
.dialog("option","node-property",name) .dialog("option","node-property",name)
@ -406,10 +442,10 @@ RED.editor = function() {
.dialog("option","title",(adding?"Add new ":"Edit ")+type+" config node") .dialog("option","title",(adding?"Add new ":"Edit ")+type+" config node")
.dialog( "open" ); .dialog( "open" );
} }
function updateConfigNodeSelect(name,type,value) { function updateConfigNodeSelect(name,type,value) {
var select = $("#node-input-"+name); var select = $("#node-input-"+name);
var node_def = RED.nodes.getType(type); var node_def = RED.nodes.getType(type);
select.children().remove(); select.children().remove();
RED.nodes.eachConfig(function(config) { RED.nodes.eachConfig(function(config) {
if (config.type == type) { if (config.type == type) {
@ -422,11 +458,11 @@ RED.editor = function() {
select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>'); select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
} }
}); });
select.append('<option value="_ADD_"'+(value==""?" selected":"")+'>Add new '+type+'...</option>'); select.append('<option value="_ADD_"'+(value==""?" selected":"")+'>Add new '+type+'...</option>');
window.setTimeout(function() { select.change();},50); window.setTimeout(function() { select.change();},50);
} }
$( "#node-config-dialog" ).dialog({ $( "#node-config-dialog" ).dialog({
modal: true, modal: true,
autoOpen: false, autoOpen: false,
@ -442,7 +478,7 @@ RED.editor = function() {
var configAdding = $(this).dialog('option','node-adding'); var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType); var configTypeDef = RED.nodes.getType(configType);
var configNode; var configNode;
if (configAdding) { if (configAdding) {
configNode = {type:configType,id:configId,users:[]}; configNode = {type:configType,id:configId,users:[]};
for (var d in configTypeDef.defaults) { for (var d in configTypeDef.defaults) {
@ -462,18 +498,14 @@ RED.editor = function() {
updateConfigNodeSelect(configProperty,configType,configId); updateConfigNodeSelect(configProperty,configType,configId);
} }
if (configTypeDef.credentials) { if (configTypeDef.credentials) {
// TODO: credentials sendCredentials(configNode,configTypeDef.credentials,"node-config-input");
// 1. call $.ajax/POST 'credentials/'+configType+"/"+configId
// with the new values - only pass back password fields
// that do not equal '__PWRD__'
// See 10-mqtt.html:150 for example code
} }
if (configTypeDef.oneditsave) { if (configTypeDef.oneditsave) {
configTypeDef.oneditsave.call(RED.nodes.node(configId)); configTypeDef.oneditsave.call(RED.nodes.node(configId));
} }
validateNode(configNode); validateNode(configNode);
RED.view.dirty(true); RED.view.dirty(true);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
}, },
@ -515,8 +547,8 @@ RED.editor = function() {
RED.sidebar.config.refresh(); RED.sidebar.config.refresh();
} }
}); });
return { return {
edit: showEditDialog, edit: showEditDialog,
editConfig: showEditConfigNodeDialog, editConfig: showEditConfigNodeDialog,

View File

@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var util = require("util"); var util = require("util");
var EventEmitter = require("events").EventEmitter; var EventEmitter = require("events").EventEmitter;
var clone = require("clone"); var clone = require("clone");
var flows = require("./flows"); var flows = require("./flows");
var credentials = require('./credentials')
var comms = require("../comms"); var comms = require("../comms");
function Node(n) { function Node(n) {
@ -120,5 +120,4 @@ Node.prototype.error = function(msg) {
Node.prototype.status = function(status,retain) { Node.prototype.status = function(status,retain) {
comms.publish("status/"+this.id,status,retain); comms.publish("status/"+this.id,status,retain);
} }
module.exports = Node; module.exports = Node;

View File

@ -18,33 +18,114 @@ var util = require("util");
var credentials = {}; var credentials = {};
var storage = null; var storage = null;
var credentialsDef = {};
var redApp = null;
var querystring = require('querystring');
var Credentials;
function getCredDef(type) {
var dashedType = type.replace(/\s+/g, '-');
return credentialsDef[dashedType];
}
function isRegistered(type) {
return getCredDef(type) != undefined;
}
function restPOST(type) {
redApp.post('/credentials/' + type + '/:id', function (req, res) {
var body = "";
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
var nodeType = type;
var nodeID = req.params.id;
var newCreds = querystring.parse(body);
var credentials = Credentials.get(nodeID) || {};
var definition = getCredDef(nodeType);
for (var cred in definition) {
if (newCreds[cred] == undefined) {
continue;
}
if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') {
continue;
}
if (newCreds[cred] == '') {
delete credentials[cred];
}
credentials[cred] = newCreds[cred];
}
Credentials.add(nodeID, credentials);
res.send(200);
});
});
}
function restGET(type) {
redApp.get('/credentials/' + type + '/:id', function (req, res) {
var nodeType = type;
var nodeID = req.params.id;
var credentials = Credentials.get(nodeID);
if (credentials == undefined) {
res.json({});
return;
}
var definition = getCredDef(nodeType);
var sendCredentials = {};
for (var cred in definition) {
if (definition[cred].type == "password") {
var key = 'has' + cred;
sendCredentials[key] = credentials[cred] != null && credentials[cred] != '';
continue;
}
sendCredentials[cred] = credentials[cred] || '';
}
res.json(sendCredentials);
});
}
function restDELETE(type) {
redApp.delete('/credentials/' + type + '/:id', function (req, res) {
var nodeID = req.params.id;
Credentials.delete(nodeID);
res.send(200);
});
}
module.exports = { module.exports = {
init: function(_storage) { init: function (_storage) {
storage = _storage; storage = _storage;
redApp = require("../server").app;
Credentials = this;
}, },
load: function() { load: function () {
return storage.getCredentials().then(function(creds) { return storage.getCredentials().then(function (creds) {
credentials = creds; credentials = creds;
}).otherwise(function(err) { }).otherwise(function (err) {
util.log("[red] Error loading credentials : "+err); util.log("[red] Error loading credentials : " + err);
}); });
}, },
add: function(id,creds) { add: function (id, creds) {
credentials[id] = creds; credentials[id] = creds;
storage.saveCredentials(credentials); storage.saveCredentials(credentials);
}, },
get: function(id) { get: function (id) {
return credentials[id]; return credentials[id];
}, },
delete: function(id) { delete: function (id) {
delete credentials[id]; delete credentials[id];
storage.saveCredentials(credentials); storage.saveCredentials(credentials);
}, },
clean: function(getNode) { clean: function (getNode) {
var deletedCredentials = false; var deletedCredentials = false;
for (var c in credentials) { for (var c in credentials) {
var n = getNode(c); var n = getNode(c);
@ -56,6 +137,13 @@ module.exports = {
if (deletedCredentials) { if (deletedCredentials) {
storage.saveCredentials(credentials); storage.saveCredentials(credentials);
} }
},
register: function (type, definition) {
var dashedType = type.replace(/\s+/g, '-');
credentialsDef[dashedType] = definition;
restDELETE(dashedType);
restGET(dashedType);
restPOST(dashedType);
} }
} }

View File

@ -36,6 +36,7 @@ module.exports = {
addCredentials: credentials.add, addCredentials: credentials.add,
getCredentials: credentials.get, getCredentials: credentials.get,
deleteCredentials: credentials.delete, deleteCredentials: credentials.delete,
registerCredentials: credentials.register,
createNode: createNode, createNode: createNode,
registerType: registry.registerType, registerType: registry.registerType,
getType: registry.get, getType: registry.get,