diff --git a/packages/cli/src/CredentialTypes.ts b/packages/cli/src/CredentialTypes.ts index 20a0e36e9e..4eb8739ad1 100644 --- a/packages/cli/src/CredentialTypes.ts +++ b/packages/cli/src/CredentialTypes.ts @@ -5,16 +5,15 @@ import { import { CredentialsOverwrites, + ICredentialsTypeData, } from './'; class CredentialTypesClass implements ICredentialTypesInterface { - credentialTypes: { - [key: string]: ICredentialType - } = {}; + credentialTypes: ICredentialsTypeData = {}; - async init(credentialTypes: { [key: string]: ICredentialType }): Promise { + async init(credentialTypes: ICredentialsTypeData): Promise { this.credentialTypes = credentialTypes; // Load the credentials overwrites if any exist diff --git a/packages/cli/src/CredentialsHelper.ts b/packages/cli/src/CredentialsHelper.ts index d9ee2b6baf..7fb35ee576 100644 --- a/packages/cli/src/CredentialsHelper.ts +++ b/packages/cli/src/CredentialsHelper.ts @@ -5,10 +5,13 @@ import { import { ICredentialDataDecryptedObject, ICredentialsHelper, + INodeParameters, + NodeHelpers, } from 'n8n-workflow'; import { CredentialsOverwrites, + CredentialTypes, Db, ICredentialsDb, } from './'; @@ -42,15 +45,51 @@ export class CredentialsHelper extends ICredentialsHelper { * * @param {string} name Name of the credentials to return data of * @param {string} type Type of the credentials to return data of + * @param {boolean} [raw] Return the data as supplied without defaults or overwrites * @returns {ICredentialDataDecryptedObject} * @memberof CredentialsHelper */ - getDecrypted(name: string, type: string): ICredentialDataDecryptedObject { + getDecrypted(name: string, type: string, raw?: boolean): ICredentialDataDecryptedObject { const credentials = this.getCredentials(name, type); + const decryptedDataOriginal = credentials.getData(this.encryptionKey); + + if (raw === true) { + return decryptedDataOriginal; + } + + return this.applyDefaultsAndOverwrites(decryptedDataOriginal, type); + } + + + /** + * Applies credential default data and overwrites + * + * @param {ICredentialDataDecryptedObject} decryptedDataOriginal The credential data to overwrite data on + * @param {string} type Type of the credentials to overwrite data of + * @returns {ICredentialDataDecryptedObject} + * @memberof CredentialsHelper + */ + applyDefaultsAndOverwrites(decryptedDataOriginal: ICredentialDataDecryptedObject, type: string): ICredentialDataDecryptedObject { + const credentialTypes = CredentialTypes(); + const credentialType = credentialTypes.getByName(type); + + if (credentialType === undefined) { + throw new Error(`The credential type "${type}" is not known and can so not be decrypted.`); + } + + // Add the default credential values + const decryptedData = NodeHelpers.getNodeParameters(credentialType.properties, decryptedDataOriginal as INodeParameters, true, false) as ICredentialDataDecryptedObject; + + if (decryptedDataOriginal.oauthTokenData !== undefined) { + // The OAuth data gets removed as it is not defined specifically as a parameter + // on the credentials so add it back in case it was set + decryptedData.oauthTokenData = decryptedDataOriginal.oauthTokenData; + } + // Load and apply the credentials overwrites if any exist const credentialsOverwrites = CredentialsOverwrites(); - return credentialsOverwrites.applyOverwrite(credentials.type, credentials.getData(this.encryptionKey)); + return credentialsOverwrites.applyOverwrite(type, decryptedData); } diff --git a/packages/cli/src/CredentialsOverwrites.ts b/packages/cli/src/CredentialsOverwrites.ts index 6d49092c37..a6e115100e 100644 --- a/packages/cli/src/CredentialsOverwrites.ts +++ b/packages/cli/src/CredentialsOverwrites.ts @@ -12,7 +12,14 @@ class CredentialsOverwritesClass { private overwriteData: ICredentialsOverwrite = {}; - async init() { + async init(overwriteData?: ICredentialsOverwrite) { + if (overwriteData !== undefined) { + // If data is already given it can directly be set instead of + // loaded from environment + this.overwriteData = overwriteData; + return; + } + const data = await GenericHelpers.getConfigValue('credentials.overwrite') as string; try { diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index e72ce7839d..abab09bd13 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -2,6 +2,7 @@ import { ICredentialDataDecryptedObject, ICredentialsDecrypted, ICredentialsEncrypted, + ICredentialType, IDataObject, IExecutionError, IRun, @@ -36,6 +37,10 @@ export interface ICustomRequest extends Request { parsedUrl: Url | undefined; } +export interface ICredentialsTypeData { + [key: string]: ICredentialType; +} + export interface ICredentialsOverwrite { [key: string]: ICredentialDataDecryptedObject; } @@ -350,7 +355,10 @@ export interface IWorkflowExecutionDataProcess { workflowData: IWorkflowBase; } + export interface IWorkflowExecutionDataProcessWithExecution extends IWorkflowExecutionDataProcess { + credentialsOverwrite: ICredentialsOverwrite; + credentialsTypeData: ICredentialsTypeData; executionId: string; nodeTypeData: ITransferNodeTypes; } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 2a041eeb8c..d9c760a7c9 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -21,7 +21,7 @@ import * as csrf from 'csrf'; import { ActiveExecutions, ActiveWorkflowRunner, - CredentialsOverwrites, + CredentialsHelper, CredentialTypes, Db, IActivationError, @@ -51,7 +51,6 @@ import { WorkflowCredentials, WebhookHelpers, WorkflowExecuteAdditionalData, - WorkflowHelpers, WorkflowRunner, GenericHelpers, } from './'; @@ -63,6 +62,7 @@ import { } from 'n8n-core'; import { + ICredentialsEncrypted, ICredentialType, IDataObject, INodeCredentials, @@ -70,6 +70,7 @@ import { INodeParameters, INodePropertyOptions, IRunData, + IWorkflowCredentials, Workflow, } from 'n8n-workflow'; @@ -913,12 +914,15 @@ class App { throw new Error('No encryption key got found to decrypt the credentials!'); } - const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data); - const savedCredentialsData = credentials.getData(encryptionKey); - - // Load the credentials overwrites if any exist - const credentialsOverwrites = CredentialsOverwrites(); - const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData); + // Decrypt the currently saved credentials + const workflowCredentials: IWorkflowCredentials = { + [result.type as string]: { + [result.name as string]: result as ICredentialsEncrypted, + }, + }; + const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey); + const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, true); + const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type); const token = new csrf(); // Generate a CSRF prevention token and send it as a OAuth2 state stringma/ERR @@ -939,8 +943,11 @@ class App { state: stateEncodedStr, }); - savedCredentialsData.csrfSecret = csrfSecret; - credentials.setData(savedCredentialsData, encryptionKey); + // Encrypt the data + const credentials = new Credentials(result.name, result.type, result.nodesAccess); + decryptedDataOriginal.csrfSecret = csrfSecret; + + credentials.setData(decryptedDataOriginal, encryptionKey); const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb; // Add special database related data @@ -992,15 +999,18 @@ class App { return ResponseHelper.sendErrorResponse(res, errorResponse); } - const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data); - const savedCredentialsData = credentials.getData(encryptionKey!); - - // Load the credentials overwrites if any exist - const credentialsOverwrites = CredentialsOverwrites(); - const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData); + // Decrypt the currently saved credentials + const workflowCredentials: IWorkflowCredentials = { + [result.type as string]: { + [result.name as string]: result as ICredentialsEncrypted, + }, + }; + const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey); + const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, true); + const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type); const token = new csrf(); - if (oauthCredentials.csrfSecret === undefined || !token.verify(oauthCredentials.csrfSecret as string, state.token)) { + if (decryptedDataOriginal.csrfSecret === undefined || !token.verify(decryptedDataOriginal.csrfSecret as string, state.token)) { const errorResponse = new ResponseHelper.ResponseError('The OAuth2 callback state is invalid!', undefined, 404); return ResponseHelper.sendErrorResponse(res, errorResponse); } @@ -1032,18 +1042,19 @@ class App { return ResponseHelper.sendErrorResponse(res, errorResponse); } - if (savedCredentialsData.oauthTokenData) { + if (decryptedDataOriginal.oauthTokenData) { // Only overwrite supplied data as some providers do for example just return the // refresh_token on the very first request and not on subsequent ones. - Object.assign(savedCredentialsData.oauthTokenData, oauthToken.data); + Object.assign(decryptedDataOriginal.oauthTokenData, oauthToken.data); } else { // No data exists so simply set - savedCredentialsData.oauthTokenData = oauthToken.data; + decryptedDataOriginal.oauthTokenData = oauthToken.data; } - _.unset(savedCredentialsData, 'csrfSecret'); + _.unset(decryptedDataOriginal, 'csrfSecret'); - credentials.setData(savedCredentialsData, encryptionKey); + const credentials = new Credentials(result.name, result.type, result.nodesAccess); + credentials.setData(decryptedDataOriginal, encryptionKey); const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb; // Add special database related data newCredentialsData.updatedAt = this.getCurrentDate(); diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index 3a09606550..f5a54ec858 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -1,5 +1,9 @@ import { ActiveExecutions, + CredentialsOverwrites, + CredentialTypes, + ICredentialsOverwrite, + ICredentialsTypeData, IProcessMessageDataHook, ITransferNodeTypes, IWorkflowExecutionDataProcess, @@ -31,12 +35,14 @@ import { fork } from 'child_process'; export class WorkflowRunner { activeExecutions: ActiveExecutions.ActiveExecutions; + credentialsOverwrites: ICredentialsOverwrite; push: Push.Push; constructor() { this.push = Push.getInstance(); this.activeExecutions = ActiveExecutions.getInstance(); + this.credentialsOverwrites = CredentialsOverwrites().getAll(); } @@ -192,8 +198,16 @@ export class WorkflowRunner { nodeTypeData = WorkflowHelpers.getNodeTypeData(data.workflowData.nodes); } + const credentialTypes = CredentialTypes(); + const credentialTypeData: ICredentialsTypeData = {}; + for (const credentialType of Object.keys(data.credentials)) { + credentialTypeData[credentialType] = credentialTypes.getByName(credentialType); + } + (data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId; (data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData; + (data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = this.credentialsOverwrites; + (data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData = credentialTypeData; // TODO: Still needs correct value const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId); diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 250e23cdaa..5748038a4f 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -1,5 +1,7 @@ import { + CredentialsOverwrites, + CredentialTypes, IWorkflowExecutionDataProcessWithExecution, NodeTypes, WorkflowExecuteAdditionalData, @@ -58,6 +60,14 @@ export class WorkflowRunnerProcess { const nodeTypes = NodeTypes(); await nodeTypes.init(nodeTypesData); + // Init credential types the workflow uses (is needed to apply default values to credentials) + const credentialTypes = CredentialTypes(); + await credentialTypes.init(inputData.credentialsTypeData); + + // Load the credentials overwrites if any exist + const credentialsOverwrites = CredentialsOverwrites(); + await credentialsOverwrites.init(); + this.workflow = new Workflow({ id: this.data.workflowData.id as string | undefined, name: this.data.workflowData.name, nodes: this.data.workflowData!.nodes, connections: this.data.workflowData!.connections, active: this.data.workflowData!.active, nodeTypes, staticData: this.data.workflowData!.staticData, settings: this.data.workflowData!.settings}); const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials); additionalData.hooks = this.getProcessForwardHooks(); diff --git a/packages/editor-ui/src/components/CredentialsInput.vue b/packages/editor-ui/src/components/CredentialsInput.vue index 16404d531d..062acd5356 100644 --- a/packages/editor-ui/src/components/CredentialsInput.vue +++ b/packages/editor-ui/src/components/CredentialsInput.vue @@ -55,7 +55,7 @@
- + {{parameter.displayName}}: @@ -128,6 +128,7 @@ import { INodeParameters, INodeProperties, INodeTypeDescription, + NodeHelpers, } from 'n8n-workflow'; import ParameterInput from '@/components/ParameterInput.vue'; @@ -199,6 +200,9 @@ export default mixins( }, credentialProperties (): INodeProperties[] { return this.credentialTypeData.properties.filter((propertyData: INodeProperties) => { + if (!this.displayCredentialParameter(propertyData)) { + return false; + } return !this.credentialTypeData.__overwrittenProperties || !this.credentialTypeData.__overwrittenProperties.includes(propertyData.name); }); }, @@ -283,7 +287,8 @@ export default mixins( name: this.name, type: (this.credentialTypeData as ICredentialType).name, nodesAccess, - data: this.propertyValue, + // Save only the none default data + data: NodeHelpers.getNodeParameters(this.credentialTypeData.properties as INodeProperties[], this.propertyValue as INodeParameters, false, false), } as ICredentialsDecrypted; let result; @@ -402,7 +407,8 @@ export default mixins( name: this.name, type: (this.credentialTypeData as ICredentialType).name, nodesAccess, - data: this.propertyValue, + // Save only the none default data + data: NodeHelpers.getNodeParameters(this.credentialTypeData.properties as INodeProperties[], this.propertyValue as INodeParameters, false, false), } as ICredentialsDecrypted; let result;