From 6a06142e1e774183eff5c6874a28670c985e666e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 26 Sep 2017 22:51:08 +0100 Subject: [PATCH] Allow credSecret to be managed via project settings --- editor/js/ui/common/popover.js | 33 ++- editor/js/ui/editor.js | 73 ----- editor/js/ui/projectSettings.js | 269 +++++++++++++++++- editor/js/ui/projects.js | 70 +---- editor/js/ui/tab-info.js | 1 - editor/sass/editor.scss | 4 + editor/sass/mixins.scss | 10 +- editor/templates/index.mst | 15 - red/api/editor/projects/index.js | 1 - red/runtime/nodes/credentials.js | 47 ++- red/runtime/nodes/flows/index.js | 1 - red/runtime/nodes/index.js | 5 +- red/runtime/storage/index.js | 8 +- .../storage/localfilesystem/projects/index.js | 80 ++++-- 14 files changed, 401 insertions(+), 216 deletions(-) diff --git a/editor/js/ui/common/popover.js b/editor/js/ui/common/popover.js index df586f668..f35179bd4 100644 --- a/editor/js/ui/common/popover.js +++ b/editor/js/ui/common/popover.js @@ -23,7 +23,7 @@ RED.popover = (function() { }, "small": { top: 5, - leftRight: 8, + leftRight: 17, leftLeft: 16 } } @@ -43,7 +43,7 @@ RED.popover = (function() { var active; var div; - var openPopup = function() { + var openPopup = function(instant) { if (active) { div = $('
').appendTo("body"); if (size !== "default") { @@ -62,7 +62,6 @@ RED.popover = (function() { var targetPos = target.offset(); var targetWidth = target.width(); var targetHeight = target.height(); - var divHeight = div.height(); var divWidth = div.width(); if (direction === 'right') { @@ -70,16 +69,23 @@ RED.popover = (function() { } else if (direction === 'left') { div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth}); } - - div.fadeIn("fast"); + if (instant) { + div.show(); + } else { + div.fadeIn("fast"); + } } } - var closePopup = function() { + var closePopup = function(instant) { if (!active) { if (div) { - div.fadeOut("fast",function() { + if (instant) { $(this).remove(); - }); + } else { + div.fadeOut("fast",function() { + $(this).remove(); + }); + } div = null; } } @@ -114,14 +120,17 @@ RED.popover = (function() { var res = { setContent: function(_content) { content = _content; + return res; }, - open: function () { + open: function (instant) { active = true; - openPopup(); + openPopup(instant); + return res; }, - close: function () { + close: function (instant) { active = false; - closePopup(); + closePopup(instant); + return res; } } return res; diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index 27d02e8ae..c24af56fc 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -500,8 +500,6 @@ RED.editor = (function() { label = RED._("markdownEditor.title"); } else if (node.type === '_buffer') { label = RED._("bufferEditor.title"); - } else if (node.type === '_project') { - label = "NLS: Edit project settings"; } else if (node.type === 'subflow') { label = RED._("subflow.editSubflow",{name:node.name}) } else if (node.type.indexOf("subflow:")===0) { @@ -2133,76 +2131,6 @@ RED.editor = (function() { RED.tray.show(trayOptions); } - function editProject(options) { - var project = options.project; - var onComplete = options.complete; - var type = "_project" - editStack.push({type:type}); - RED.view.state(RED.state.EDITING); - - var trayOptions = { - title: options.title || getEditStackTitle(), - buttons: [ - { - id: "node-dialog-cancel", - text: RED._("common.label.cancel"), - click: function() { - RED.tray.close(); - } - }, - { - id: "node-dialog-ok", - text: RED._("common.label.done"), - class: "primary", - click: function() { - onComplete("Whheeeeee"); - RED.tray.close(); - } - } - ], - resize: function(dimensions) { - // editTrayWidthCache[type] = dimensions.width; - // - // var rows = $("#dialog-form>div:not(.node-text-editor-row)"); - // var editorRow = $("#dialog-form>div.node-text-editor-row"); - // var height = $("#dialog-form").height(); - // for (var i=0;i 0) { + depsList.editableList('addItem',{index:1, label:"Unknown Dependencies"}); // TODO: nls + } + if (unusedCount > 0) { + depsList.editableList('addItem',{index:3, label:"Unused dependencies"}); // TODO: nls + } + if (totalCount === 0) { + depsList.editableList('addItem',{index:0, label:"None"}); // TODO: nls + } + } function editDependencies(activeProject,depsJSON,container,depsList) { + var json = depsJSON||JSON.stringify(activeProject.dependencies||{},"",4); + if (json === "{}") { + json = "{\n\n}"; + } RED.editor.editJSON({ title: RED._('sidebar.project.editDependencies'), - value: depsJSON||JSON.stringify(activeProject.dependencies||{},"",4), + value: json, requireValid: true, complete: function(v) { try { @@ -286,7 +311,7 @@ RED.projects.settings = (function() { var done = function(err,res) { if (err) { - editDependencies(activeProject,v,container,depsList); + return editDependencies(activeProject,v,container,depsList); } activeProject.dependencies = parsed; updateProjectDependencies(activeProject,depsList); @@ -332,7 +357,11 @@ RED.projects.settings = (function() { // console.log(entry); var headerRow = $('
',{class:"palette-module-header"}).appendTo(row); if (entry.label) { - row.parent().addClass("palette-module-section"); + if (entry.index === 0) { + headerRow.addClass("red-ui-search-empty") + } else { + row.parent().addClass("palette-module-section"); + } headerRow.text(entry.label); if (entry.index === 1) { var addButton = $('').appendTo(headerRow).click(function(evt) { @@ -358,6 +387,7 @@ RED.projects.settings = (function() { }); } } else { + headerRow.addClass("palette-module-header"); headerRow.toggleClass("palette-module-unused",entry.count === 0); entry.element = headerRow; var titleRow = $('
').appendTo(headerRow); @@ -392,6 +422,216 @@ RED.projects.settings = (function() { } + function createSettingsPane(activeProject) { + var pane = $('
'); + $('

').text("Credentials").appendTo(pane); + var row = $('').appendTo(pane); + if (activeProject.settings.credentialsEncrypted) { + $(' Credentials are encrypted').appendTo(row); + } else { + $(' Credentials are not encrypted').appendTo(row); + } + var resetButton; + var action; + var changeButton = $('') + .text(activeProject.settings.credentialsEncrypted?"Change key":"Enable encryption") + .appendTo(row) + .click(function(evt) { + evt.preventDefault(); + newKey.val(""); + if (currentKey) { + currentKey.val(""); + currentKey.removeClass("input-error"); + } + checkInputs(); + saveButton.text("Save"); + + $(".project-settings-credentials-row").show(); + $(".project-settings-credentials-current-row").show(); + $(this).prop('disabled',true); + if (resetButton) { + resetButton.prop('disabled',true); + } + action = 'change'; + }); + if (activeProject.settings.credentialsEncrypted) { + resetButton = $('') + .text("Reset key") + .appendTo(row) + .click(function(evt) { + evt.preventDefault(); + newKey.val(""); + if (currentKey) { + currentKey.val(""); + currentKey.removeClass("input-error"); + } + checkInputs(); + saveButton.text("Reset key"); + + $(".project-settings-credentials-row").show(); + $(".project-settings-credentials-reset-row").show(); + + $(this).prop('disabled',true); + changeButton.prop('disabled',true); + action = 'reset'; + }); + } + + if (activeProject.settings.credentialsInvalid) { + row = $('').appendTo(pane); + $('
The current key is not valid. Set the correct key or reset credentials.
').appendTo(row); + } + + var credentialsContainer = $('
',{style:"position:relative"}).appendTo(pane); + var currentKey; + var newKey; + + var checkInputs = function() { + var valid = true; + if (newKey.val().length === 0) { + valid = false; + } + if (currentKey && currentKey.val() === 0) { + valid = false; + } + saveButton.toggleClass('disabled',!valid); + } + + if (activeProject.settings.credentialsEncrypted) { + if (!activeProject.settings.credentialsInvalid) { + row = $('').appendTo(credentialsContainer); + $('').appendTo(row); + currentKey = $('').appendTo(row); + currentKey.on("change keyup paste",function() { + if (popover) { + popover.close(); + popover = null; + $(this).removeClass('input-error'); + } + checkInputs(); + }); + } + row = $('').appendTo(credentialsContainer); + $('
Resetting the key will delete all existing credentials
').appendTo(row); + + } + // $('').appendTo(row); + + row = $('').appendTo(credentialsContainer); + $('').text((activeProject.settings.credentialsEncrypted&& !activeProject.settings.credentialsInvalid)?"New key":"Encryption key").appendTo(row); + newKey = $('').appendTo(row).on("change keyup paste",checkInputs); + + row = $('').appendTo(credentialsContainer); + var bg = $('
').appendTo(row); + $('') + .appendTo(bg) + .click(function(evt) { + evt.preventDefault(); + if (popover) { + popover.close(); + popover = null; + } + changeButton.prop('disabled',false); + if (resetButton) { + resetButton.prop('disabled',false); + } + $(".project-settings-credentials-row").hide(); + $(".project-settings-credentials-current-row").hide(); + $(".project-settings-credentials-reset-row").hide(); + }); + var saveButton = $('') + .text("Save") + .appendTo(bg) + .click(function(evt) { + evt.preventDefault(); + if ($(this).hasClass('disabled')) { + return; + } + var spinner = addSpinnerOverlay(credentialsContainer); + var payload = { + credentialSecret: newKey.val() + }; + if (activeProject.settings.credentialsInvalid) { + RED.deploy.setDeployInflight(true); + } + + if (activeProject.settings.credentialsEncrypted) { + if (action === 'reset') { + payload.resetCredentialSecret = true; + } else if (!activeProject.settings.credentialsInvalid) { + payload.currentCredentialSecret = currentKey.val(); + } + } + var done = function(err,res) { + spinner.remove(); + if (err) { + console.log(err); + return; + } + } + utils.sendRequest({ + url: "projects/"+activeProject.name, + type: "PUT", + responses: { + 0: function(error) { + done(error,null); + }, + 200: function(data) { + if (popover) { + popover.close(); + popover = null; + } + changeButton.prop('disabled',false); + if (resetButton) { + resetButton.prop('disabled',false); + } + $(".project-settings-credentials-row").hide(); + $(".project-settings-credentials-current-row").hide(); + $(".project-settings-credentials-reset-row").hide(); + }, + 400: { + 'unexpected_error': function(error) { + done(error,null); + }, + 'missing_current_credential_key': function(error) { + currentKey.addClass("input-error"); + popover = RED.popover.create({ + target: currentKey, + direction: 'right', + size: 'small', + content: "Incorrect key" + }).open(); + done(); + } + }, + } + },payload).always(function() { + if (activeProject.settings.credentialsInvalid) { + RED.deploy.setDeployInflight(false); + } + }); + }); + + + + // $('

').text("Credentials").appendTo(pane); + // row = $('').appendTo(pane); + // $(' Credentials are not encrypted').appendTo(row); + // $('').appendTo(row); + + + // $('

').text("Repository").appendTo(pane); + // row = $('').appendTo(pane); + // var input; + // $('').appendTo(row); + // $('').appendTo(row); + + + return pane; + } + + var popover; + var utils; var modulesInUse = {}; function init(_utils) { @@ -408,6 +648,17 @@ RED.projects.settings = (function() { get: createDependenciesPane, close: function() { } }); + addPane({ + id:'settings', + title: "Settings", // TODO: nls + get: createSettingsPane, + close: function() { + if (popover) { + popover.close(); + popover = null; + } + } + }); RED.events.on('nodes:add', function(n) { if (!/^subflow:/.test(n.type)) { @@ -444,6 +695,10 @@ RED.projects.settings = (function() { } return { init: init, - show: show + show: show, + switchProject: function(name) { + // TODO: not ideal way to trigger this; should there be an editor-wide event? + modulesInUse = {}; + } }; })(); diff --git a/editor/js/ui/projects.js b/editor/js/ui/projects.js index 8254deeb4..a78d094ec 100644 --- a/editor/js/ui/projects.js +++ b/editor/js/ui/projects.js @@ -273,61 +273,6 @@ RED.projects = (function() { ] } })(), - 'credentialSecret': { - content: function() { - // Provide new secret or reset credentials. - var container = $('
'); - var row = $('
').appendTo(container); - // $('').appendTo(row); - $('').appendTo(row); - var projectSecret = $('').appendTo(row); - - return container; - }, - buttons: [ - { - // id: "clipboard-dialog-cancel", - text: RED._("common.label.cancel"), - click: function() { - dialog.dialog( "close" ); - } - }, - { - // id: "clipboard-dialog-cancel", - text: "Update", - click: function() { - var done = function(err,data) { - if (err) { - console.log(err); - } - dialog.dialog( "close" ); - } - RED.deploy.setDeployInflight(true); - sendRequest({ - url: "projects/"+activeProject.name, - type: "PUT", - responses: { - 200: function(data) { - done(null,data); - }, - 400: { - 'credentials_load_failed': function(error) { - done(error,null); - }, - 'unexpected_error': function(error) { - done(error,null); - } - }, - } - },{credentialSecret:$('#projects-dialog-secret').val()}).always(function() { - RED.deploy.setDeployInflight(false); - }) - - - } - } - ] - }, 'open': { content: function() { return createProjectList({ @@ -371,7 +316,7 @@ RED.projects = (function() { function switchProject(name,done) { RED.deploy.setDeployInflight(true); - modulesInUse = {}; + RED.projects.settings.switchProject(name); sendRequest({ url: "projects/"+name, type: "PUT", @@ -497,7 +442,7 @@ RED.projects = (function() { console.log(err); }).always(function() { var delta = Date.now() - start; - delta = Math.max(0,1000-delta); + delta = Math.max(0,500-delta); setTimeout(function() { // dialogBody.show(); $(".projects-dialog-spinner").hide(); @@ -967,7 +912,6 @@ function refresh() { // updateProjectSummary(); // updateProjectDescription(); // updateProjectDependencies(); - console.log("project triggering a info refresh for ",activeProject) RED.sidebar.info.refresh(); }); } @@ -987,19 +931,13 @@ function refresh() { selectProject: function() { show('open') }, - showCredentialsPrompt: function() { - show('credentialSecret'); + showCredentialsPrompt: function() { //TODO: rename this function + RED.projects.settings.show('settings'); }, // showSidebar: showSidebar, refresh: refresh, editProject: function() { RED.projects.settings.show(); - // RED.editor.editProject({ - // project: activeProject, - // complete: function(result) { - // console.log(result); - // } - // }); }, getActiveProject: function() { return activeProject; diff --git a/editor/js/ui/tab-info.js b/editor/js/ui/tab-info.js index 998d687ea..31b801d31 100644 --- a/editor/js/ui/tab-info.js +++ b/editor/js/ui/tab-info.js @@ -109,7 +109,6 @@ RED.sidebar.info = (function() { return el; } function refresh(node) { - console.log('sidebar.info.refresh'); if (node === undefined) { refreshSelection(); return; diff --git a/editor/sass/editor.scss b/editor/sass/editor.scss index 9566d38dc..294bf75d0 100644 --- a/editor/sass/editor.scss +++ b/editor/sass/editor.scss @@ -196,6 +196,10 @@ text-decoration: underline; } +.form-warning { + border-color: #d6615f; +} + .node-text-editor { border:1px solid #ccc; border-radius:5px; diff --git a/editor/sass/mixins.scss b/editor/sass/mixins.scss index 5f96329d5..fdfc1ad64 100644 --- a/editor/sass/mixins.scss +++ b/editor/sass/mixins.scss @@ -48,26 +48,26 @@ text-decoration: none; cursor:pointer; - &.disabled { + &.disabled, &:disabled { cursor: default; color: $workspace-button-color-disabled !important; } &:hover, &:focus { text-decoration: none; } - &:not(.disabled):hover { + &:not(.disabled):not(:disabled):hover, { color: $workspace-button-color-hover !important; background: $workspace-button-background-hover; } - &:not(.disabled):focus { + &:not(.disabled):not(:disabled):focus { color: $workspace-button-color-focus !important; } - &:not(.disabled):active { + &:not(.disabled):not(:disabled):active { color: $workspace-button-color-active !important; background: $workspace-button-background-active; text-decoration: none; } - &.selected:not(.disabled) { + &.selected:not(.disabled):not(:disabled) { color: $workspace-button-color-selected !important; background: $workspace-button-background-active; } diff --git a/editor/templates/index.mst b/editor/templates/index.mst index 4a8abe80b..502144117 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -234,21 +234,6 @@
- - diff --git a/red/api/editor/projects/index.js b/red/api/editor/projects/index.js index 98f3b2814..a947e82d9 100644 --- a/red/api/editor/projects/index.js +++ b/red/api/editor/projects/index.js @@ -49,7 +49,6 @@ module.exports = { app.post("/", function(req,res) { // Create project runtime.storage.projects.createProject(req.body).then(function(name) { - console.log("Created project",name); runtime.storage.projects.getProject(name).then(function(data) { res.json(data); }); diff --git a/red/runtime/nodes/credentials.js b/red/runtime/nodes/credentials.js index 25f20d5f0..b4d6c1841 100644 --- a/red/runtime/nodes/credentials.js +++ b/red/runtime/nodes/credentials.js @@ -38,6 +38,28 @@ function decryptCredentials(key,credentials) { return JSON.parse(decrypted); } +function markProjectSecretValid(project,valid) { + try { + var projects = settings.get('projects'); + if (projects) { + if (valid) { + if (!projects.projects[project].credentialSecretInvalid) { + return when.resolve(); + } else { + delete projects.projects[project].credentialSecretInvalid; + } + } else { + projects.projects[project].credentialSecretInvalid = true; + } + return settings.set('projects',projects); + } else { + return when.resolve(); + } + } catch(err) { + return when.resolve(); + } +} + var api = module.exports = { init: function(runtime) { log = runtime.log; @@ -75,16 +97,17 @@ var api = module.exports = { } var projectKey = false; + var activeProject; try { var projects = settings.get('projects'); if (projects && projects.activeProject) { + activeProject = projects.activeProject; projectKey = projects.projects[projects.activeProject].credentialSecret; } } catch(err) { } if (projectKey) { log.debug("red/runtime/nodes/credentials.load : using active project key - ignoring user provided key"); - console.log(projectKey); userKey = projectKey; } @@ -171,21 +194,32 @@ var api = module.exports = { encryptedCredentials = credentials; } return setupEncryptionPromise.then(function() { + var clearInvalidFlag = false; if (credentials.hasOwnProperty("$")) { // These are encrypted credentials try { credentialCache = decryptCredentials(encryptionKey,credentials) + clearInvalidFlag = true; } catch(err) { credentialCache = {}; dirty = true; log.warn(log._("nodes.credentials.error",{message:err.toString()})) var error = new Error("Failed to decrypt credentials"); error.code = "credentials_load_failed"; + if (projectKey) { + // This is a project with a bad key. Mark it as invalid + return markProjectSecretValid(activeProject,false).then(function() { + return when.reject(error); + }) + } return when.reject(error); } } else { credentialCache = credentials; } + if (clearInvalidFlag) { + return markProjectSecretValid(activeProject,true) + } }); }, @@ -222,6 +256,10 @@ var api = module.exports = { dirty = true; }, + clear: function() { + credentialCache = {}; + dirty = true; + }, /** * Deletes any credentials for nodes that no longer exist * @param config a flow config @@ -321,9 +359,14 @@ var api = module.exports = { dirty: function() { return dirty; }, - + setKey: function(key) { + encryptionKey = crypto.createHash('sha256').update(key).digest(); + encryptionEnabled = true; + dirty = true; + }, export: function() { var result = credentialCache; + if (encryptionEnabled) { if (dirty) { try { diff --git a/red/runtime/nodes/flows/index.js b/red/runtime/nodes/flows/index.js index 80753e38a..6131e18e4 100644 --- a/red/runtime/nodes/flows/index.js +++ b/red/runtime/nodes/flows/index.js @@ -231,7 +231,6 @@ function handleStatus(node,statusMessage) { function start(type,diff,muteLog) { - console.log("START----") //dumpActiveNodes(); type = type||"full"; started = true; diff --git a/red/runtime/nodes/index.js b/red/runtime/nodes/index.js index df06b81f5..29d58af6e 100644 --- a/red/runtime/nodes/index.js +++ b/red/runtime/nodes/index.js @@ -172,5 +172,8 @@ module.exports = { addCredentials: credentials.add, getCredentials: credentials.get, deleteCredentials: credentials.delete, - getCredentialDefinition: credentials.getDefinition + getCredentialDefinition: credentials.getDefinition, + setCredentialSecret: credentials.setKey, + clearCredentials: credentials.clear, + exportCredentials: credentials.export }; diff --git a/red/runtime/storage/index.js b/red/runtime/storage/index.js index 1262ab91a..8684cdd47 100644 --- a/red/runtime/storage/index.js +++ b/red/runtime/storage/index.js @@ -84,16 +84,16 @@ var storageModuleInterface = { return credentialSavePromise.then(function() { return storageModule.saveFlows(flows).then(function() { - return crypto.createHash('md5').update(JSON.stringify(config)).digest("hex"); + return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex"); }) }); }, // getCredentials: function() { // return storageModule.getCredentials(); // }, - // saveCredentials: function(credentials) { - // return storageModule.saveCredentials(credentials); - // }, + saveCredentials: function(credentials) { + return storageModule.saveCredentials(credentials); + }, getSettings: function() { if (settingsAvailable) { return storageModule.getSettings(); diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index 517d2dd64..60ed2e8f2 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -131,10 +131,20 @@ function getProject(project) { projectSettings = globalProjectSettings.projects[project]||{}; } - // console.log(projectSettings); var projectData = { - name: project + name: project, + settings: {} }; + + if (typeof projectSettings.credentialSecret === "string") { + projectData.settings.credentialsEncrypted = true; + } else { + projectData.settings.credentialsEncrypted = false; + } + if (projectSettings.credentialSecretInvalid) { + projectData.settings.credentialsInvalid = true; + } + var promises = []; checkProjectFiles(project).then(function(missingFiles) { if (missingFiles.length > 0) { @@ -158,17 +168,6 @@ function getProject(project) { when.settle(promises).then(function() { resolve(projectData); }) - // if (missingFiles.indexOf('flow_cred.json') === -1) { - // promises.push(nodeFn.call(fs.readFile,fspath.join(projectPath,"flow_cred.json"),"utf8").then(function(creds) { - // var credentials = util.parseJSON(creds); - // if (credentials.hasOwnProperty('$')) { - // // try { - // // decryptCredentials - // // } - // } - // })); - // } - }); // fs.stat(projectPath,function(err,stat) { @@ -185,22 +184,45 @@ function getProject(project) { }); } -var encryptionAlgorithm = "aes-256-ctr"; -function decryptCredentials(key,credentials) { - var creds = credentials["$"]; - var initVector = new Buffer(creds.substring(0, 32),'hex'); - creds = creds.substring(32); - var decipher = crypto.createDecipheriv(encryptionAlgorithm, key, initVector); - var decrypted = decipher.update(creds, 'base64', 'utf8') + decipher.final('utf8'); - return JSON.parse(decrypted); -} +function setCredentialSecret(project,data) { //existingSecret,secret) { + var existingSecret = data.currentCredentialSecret; + var isReset = data.resetCredentialSecret; + var secret = data.credentialSecret; + var wasInvalid = false; -function setCredentialSecret(project,secret) { var globalProjectSettings = settings.get("projects"); globalProjectSettings.projects = globalProjectSettings.projects || {}; - globalProjectSettings.projects[project] = globalProjectSettings.projects[project] || {}; + if (globalProjectSettings.projects.hasOwnProperty(project)) { + if (!isReset && + globalProjectSettings.projects[project].credentialSecret && + !globalProjectSettings.projects[project].credentialSecretInvalid && + globalProjectSettings.projects[project].credentialSecret !== existingSecret) { + var e = new Error("Cannot change credentialSecret without current key"); + e.code = "missing_current_credential_key"; + throw e; + } + } else { + globalProjectSettings.projects[project] = {}; + } + globalProjectSettings.projects[project].credentialSecret = secret; - return settings.set("projects",globalProjectSettings); + wasInvalid = globalProjectSettings.projects[project].credentialSecretInvalid; + delete globalProjectSettings.projects[project].credentialSecretInvalid; + + return settings.set("projects",globalProjectSettings).then(function() { + if (isReset || !wasInvalid) { + if (isReset) { + runtime.nodes.clearCredentials(); + } + runtime.nodes.setCredentialSecret(secret); + return runtime.nodes.exportCredentials() + .then(runtime.storage.saveCredentials) + .then(function() { + return wasInvalid; + }); + } + return wasInvalid; + }); } function createProject(metadata) { @@ -218,7 +240,7 @@ function createProject(metadata) { } createProjectDirectory(project).then(function() { if (metadata.credentialSecret) { - return setCredentialSecret(project,metadata.credentialSecret); + return setCredentialSecret(project,{credentialSecret: credentialSecret}); } return when.resolve(); }).then(function() { @@ -422,8 +444,10 @@ function updateProject(project,data) { return checkProjectExists(project).then(function() { if (data.credentialSecret) { // TODO: this path assumes we aren't trying to migrate the secret - return setCredentialSecret(project,data.credentialSecret).then(function() { - return reloadActiveProject(project); + return setCredentialSecret(project,data).then(function(wasInvalid) { + if (wasInvalid) { + return reloadActiveProject(project); + } }) } else if (data.hasOwnProperty('description')) { var projectPath = fspath.join(projectsDir,project);