Make it possible to set credentials to fixed values

This commit is contained in:
Jan Oberhauser 2020-01-25 23:48:38 -08:00
parent 8228b8505f
commit eb285ef711
19 changed files with 316 additions and 117 deletions

View file

@ -6,6 +6,7 @@ import {
import { import {
ActiveExecutions, ActiveExecutions,
CredentialsOverwrites,
Db, Db,
GenericHelpers, GenericHelpers,
IWorkflowBase, IWorkflowBase,
@ -100,6 +101,10 @@ export class Execute extends Command {
// Wait till the n8n-packages have been read // Wait till the n8n-packages have been read
await loadNodesAndCredentialsPromise; await loadNodesAndCredentialsPromise;
// Load the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites();
await credentialsOverwrites.init();
// Add the found types to an instance other parts of the application can use // Add the found types to an instance other parts of the application can use
const nodeTypes = NodeTypes(); const nodeTypes = NodeTypes();
await nodeTypes.init(loadNodesAndCredentials.nodeTypes); await nodeTypes.init(loadNodesAndCredentials.nodeTypes);

View file

@ -11,6 +11,7 @@ import * as config from '../config';
import { import {
ActiveWorkflowRunner, ActiveWorkflowRunner,
CredentialTypes, CredentialTypes,
CredentialsOverwrites,
Db, Db,
GenericHelpers, GenericHelpers,
LoadNodesAndCredentials, LoadNodesAndCredentials,
@ -112,6 +113,10 @@ export class Start extends Command {
const loadNodesAndCredentials = LoadNodesAndCredentials(); const loadNodesAndCredentials = LoadNodesAndCredentials();
await loadNodesAndCredentials.init(); await loadNodesAndCredentials.init();
// Load the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites();
await credentialsOverwrites.init();
// Add the found types to an instance other parts of the application can use // Add the found types to an instance other parts of the application can use
const nodeTypes = NodeTypes(); const nodeTypes = NodeTypes();
await nodeTypes.init(loadNodesAndCredentials.nodeTypes); await nodeTypes.init(loadNodesAndCredentials.nodeTypes);

View file

@ -54,6 +54,19 @@ const config = convict({
}, },
}, },
credentials: {
overwrite: {
// Allows to set default values for credentials which
// get automatically prefilled and the user does not get
// displayed and can not change.
// Format: { CREDENTIAL_NAME: { PARAMTER: VALUE }}
doc: 'Overwrites for credentials',
format: '*',
default: '{}',
env: 'CREDENTIALS_OVERWRITE'
}
},
executions: { executions: {
// If a workflow executes all the data gets saved by default. This // If a workflow executes all the data gets saved by default. This
// could be a problem when a workflow gets executed a lot and processes // could be a problem when a workflow gets executed a lot and processes

View file

@ -3,6 +3,9 @@ import {
ICredentialTypes as ICredentialTypesInterface, ICredentialTypes as ICredentialTypesInterface,
} from 'n8n-workflow'; } from 'n8n-workflow';
import {
CredentialsOverwrites,
} from './';
class CredentialTypesClass implements ICredentialTypesInterface { class CredentialTypesClass implements ICredentialTypesInterface {
@ -13,6 +16,19 @@ class CredentialTypesClass implements ICredentialTypesInterface {
async init(credentialTypes: { [key: string]: ICredentialType }): Promise<void> { async init(credentialTypes: { [key: string]: ICredentialType }): Promise<void> {
this.credentialTypes = credentialTypes; this.credentialTypes = credentialTypes;
// Load the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites().getAll();
for (const credentialType of Object.keys(credentialsOverwrites)) {
if (credentialTypes[credentialType] === undefined) {
continue;
}
// Add which properties got overwritten that the Editor-UI knows
// which properties it should hide
credentialTypes[credentialType].__overwrittenProperties = Object.keys(credentialsOverwrites[credentialType]);
}
} }
getAll(): ICredentialType[] { getAll(): ICredentialType[] {

View file

@ -0,0 +1,81 @@
import {
Credentials,
} from 'n8n-core';
import {
ICredentialDataDecryptedObject,
ICredentialsHelper,
} from 'n8n-workflow';
import {
CredentialsOverwrites,
Db,
ICredentialsDb,
} from './';
export class CredentialsHelper extends ICredentialsHelper {
/**
* Returns the credentials instance
*
* @param {string} name Name of the credentials to return instance of
* @param {string} type Type of the credentials to return instance of
* @returns {Credentials}
* @memberof CredentialsHelper
*/
getCredentials(name: string, type: string): Credentials {
if (!this.workflowCredentials[type]) {
throw new Error(`No credentials of type "${type}" exist.`);
}
if (!this.workflowCredentials[type][name]) {
throw new Error(`No credentials with name "${name}" exist for type "${type}".`);
}
const credentialData = this.workflowCredentials[type][name];
return new Credentials(credentialData.name, credentialData.type, credentialData.nodesAccess, credentialData.data);
}
/**
* Returns the decrypted credential data with applied overwrites
*
* @param {string} name Name of the credentials to return data of
* @param {string} type Type of the credentials to return data of
* @returns {ICredentialDataDecryptedObject}
* @memberof CredentialsHelper
*/
getDecrypted(name: string, type: string): ICredentialDataDecryptedObject {
const credentials = this.getCredentials(name, type);
// Load and apply the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites();
return credentialsOverwrites.applyOverwrite(credentials.type, credentials.getData(this.encryptionKey));
}
/**
* Updates credentials in the database
*
* @param {string} name Name of the credentials to set data of
* @param {string} type Type of the credentials to set data of
* @param {ICredentialDataDecryptedObject} data The data to set
* @returns {Promise<void>}
* @memberof CredentialsHelper
*/
async updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void> {
const credentials = await this.getCredentials(name, type);
credentials.setData(data, this.encryptionKey);
const newCredentialsData = credentials.getDataToSave() as ICredentialsDb;
// Add special database related data
newCredentialsData.updatedAt = new Date();
// TODO: also add user automatically depending on who is logged in, if anybody is logged in
// Save the credentials in DB
await Db.collections.Credentials!.save(newCredentialsData);
}
}

View file

@ -0,0 +1,56 @@
import {
ICredentialDataDecryptedObject,
} from 'n8n-workflow';
import {
ICredentialsOverwrite,
GenericHelpers,
} from './';
class CredentialsOverwritesClass {
private overwriteData: ICredentialsOverwrite = {};
async init() {
const data = await GenericHelpers.getConfigValue('credentials.overwrite') as string;
try {
this.overwriteData = JSON.parse(data);
} catch (error) {
throw new Error(`The credentials-overwrite is not valid JSON.`);
}
}
applyOverwrite(type: string, data: ICredentialDataDecryptedObject) {
const overwrites = this.get(type);
if (overwrites === undefined) {
return data;
}
const returnData = JSON.parse(JSON.stringify(data));
Object.assign(returnData, overwrites);
return returnData;
}
get(type: string): ICredentialDataDecryptedObject | undefined {
return this.overwriteData[type];
}
getAll(): ICredentialsOverwrite {
return this.overwriteData;
}
}
let credentialsOverwritesInstance: CredentialsOverwritesClass | undefined;
export function CredentialsOverwrites(): CredentialsOverwritesClass {
if (credentialsOverwritesInstance === undefined) {
credentialsOverwritesInstance = new CredentialsOverwritesClass();
}
return credentialsOverwritesInstance;
}

View file

@ -1,4 +1,5 @@
import { import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted, ICredentialsDecrypted,
ICredentialsEncrypted, ICredentialsEncrypted,
IDataObject, IDataObject,
@ -34,6 +35,9 @@ export interface ICustomRequest extends Request {
parsedUrl: Url | undefined; parsedUrl: Url | undefined;
} }
export interface ICredentialsOverwrite {
[key: string]: ICredentialDataDecryptedObject;
}
export interface IDatabaseCollections { export interface IDatabaseCollections {
Credentials: Repository<ICredentialsDb> | null; Credentials: Repository<ICredentialsDb> | null;

View file

@ -137,7 +137,7 @@ class LoadNodesAndCredentialsClass {
} }
} }
this.credentialTypes[credentialName] = tempCredential; this.credentialTypes[tempCredential.name] = tempCredential;
} }

View file

@ -15,7 +15,7 @@ class NodeTypesClass implements INodeTypes {
// Some nodeTypes need to get special parameters applied like the // Some nodeTypes need to get special parameters applied like the
// polling nodes the polling times // polling nodes the polling times
for (const nodeTypeData of Object.values(nodeTypes)) { for (const nodeTypeData of Object.values(nodeTypes)) {
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type) const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeTypeData.type);
if (applyParameters.length) { if (applyParameters.length) {
nodeTypeData.type.description.properties.unshift.apply(nodeTypeData.type.description.properties, applyParameters); nodeTypeData.type.description.properties.unshift.apply(nodeTypeData.type.description.properties, applyParameters);

View file

@ -18,6 +18,7 @@ import * as csrf from 'csrf';
import { import {
ActiveExecutions, ActiveExecutions,
ActiveWorkflowRunner, ActiveWorkflowRunner,
CredentialsOverwrites,
CredentialTypes, CredentialTypes,
Db, Db,
IActivationError, IActivationError,
@ -648,6 +649,10 @@ class App {
this.app.post('/rest/credentials', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<ICredentialsResponse> => { this.app.post('/rest/credentials', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<ICredentialsResponse> => {
const incomingData = req.body; const incomingData = req.body;
if (!incomingData.name || incomingData.name.length < 3) {
throw new ResponseHelper.ResponseError(`Credentials name must be at least 3 characters long.`, undefined, 400);
}
// Add the added date for node access permissions // Add the added date for node access permissions
for (const nodeAccess of incomingData.nodesAccess) { for (const nodeAccess of incomingData.nodesAccess) {
nodeAccess.date = this.getCurrentDate(); nodeAccess.date = this.getCurrentDate();
@ -684,6 +689,7 @@ class App {
// Save the credentials in DB // Save the credentials in DB
const result = await Db.collections.Credentials!.save(newCredentialsData); const result = await Db.collections.Credentials!.save(newCredentialsData);
result.data = incomingData.data;
// Convert to response format in which the id is a string // Convert to response format in which the id is a string
(result as unknown as ICredentialsResponse).id = result.id.toString(); (result as unknown as ICredentialsResponse).id = result.id.toString();
@ -805,14 +811,6 @@ class App {
const results = await Db.collections.Credentials!.find(findQuery) as unknown as ICredentialsResponse[]; const results = await Db.collections.Credentials!.find(findQuery) as unknown as ICredentialsResponse[];
let encryptionKey = undefined;
if (req.query.includeData === true) {
encryptionKey = await UserSettings.getEncryptionKey();
if (encryptionKey === undefined) {
throw new Error('No encryption key got found to decrypt the credentials!');
}
}
let result; let result;
for (result of results) { for (result of results) {
(result as ICredentialsDecryptedResponse).id = result.id.toString(); (result as ICredentialsDecryptedResponse).id = result.id.toString();
@ -866,19 +864,17 @@ class App {
} }
const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data); const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data);
(result as ICredentialsDecryptedDb).data = credentials.getData(encryptionKey!); const savedCredentialsData = credentials.getData(encryptionKey);
(result as ICredentialsDecryptedResponse).id = result.id.toString();
const oauthCredentials = (result as ICredentialsDecryptedDb).data; // Load the credentials overwrites if any exist
if (oauthCredentials === undefined) { const credentialsOverwrites = CredentialsOverwrites();
throw new Error('Unable to read OAuth credentials'); const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData);
}
const token = new csrf(); const token = new csrf();
// Generate a CSRF prevention token and send it as a OAuth2 state stringma/ERR // Generate a CSRF prevention token and send it as a OAuth2 state stringma/ERR
oauthCredentials.csrfSecret = token.secretSync(); const csrfSecret = token.secretSync();
const state = { const state = {
token: token.create(oauthCredentials.csrfSecret), token: token.create(csrfSecret),
cid: req.query.id cid: req.query.id
}; };
const stateEncodedStr = Buffer.from(JSON.stringify(state)).toString('base64') as string; const stateEncodedStr = Buffer.from(JSON.stringify(state)).toString('base64') as string;
@ -893,7 +889,8 @@ class App {
state: stateEncodedStr, state: stateEncodedStr,
}); });
credentials.setData(oauthCredentials, encryptionKey); savedCredentialsData.csrfSecret = csrfSecret;
credentials.setData(savedCredentialsData, encryptionKey);
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb; const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
// Add special database related data // Add special database related data
@ -939,12 +936,11 @@ class App {
} }
const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data); const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data);
(result as ICredentialsDecryptedDb).data = credentials.getData(encryptionKey!); const savedCredentialsData = credentials.getData(encryptionKey!);
const oauthCredentials = (result as ICredentialsDecryptedDb).data;
if (oauthCredentials === undefined) { // Load the credentials overwrites if any exist
const errorResponse = new ResponseHelper.ResponseError('Unable to read OAuth credentials!', undefined, 503); const credentialsOverwrites = CredentialsOverwrites();
return ResponseHelper.sendErrorResponse(res, errorResponse); const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData);
}
const token = new csrf(); const token = new csrf();
if (oauthCredentials.csrfSecret === undefined || !token.verify(oauthCredentials.csrfSecret as string, state.token)) { if (oauthCredentials.csrfSecret === undefined || !token.verify(oauthCredentials.csrfSecret as string, state.token)) {
@ -968,9 +964,10 @@ class App {
return ResponseHelper.sendErrorResponse(res, errorResponse); return ResponseHelper.sendErrorResponse(res, errorResponse);
} }
oauthCredentials.oauthTokenData = JSON.stringify(oauthToken.data); savedCredentialsData.oauthTokenData = JSON.stringify(oauthToken.data);
_.unset(oauthCredentials, 'csrfSecret'); _.unset(savedCredentialsData, 'csrfSecret');
credentials.setData(oauthCredentials, encryptionKey);
credentials.setData(savedCredentialsData, encryptionKey);
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb; const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
// Add special database related data // Add special database related data
newCredentialsData.updatedAt = this.getCurrentDate(); newCredentialsData.updatedAt = this.getCurrentDate();

View file

@ -1,6 +1,6 @@
import { import {
CredentialsHelper,
Db, Db,
ICredentialsDb,
IExecutionDb, IExecutionDb,
IExecutionFlattedDb, IExecutionFlattedDb,
IPushDataExecutionFinished, IPushDataExecutionFinished,
@ -14,13 +14,11 @@ import {
} from './'; } from './';
import { import {
Credentials,
UserSettings, UserSettings,
WorkflowExecute, WorkflowExecute,
} from 'n8n-core'; } from 'n8n-core';
import { import {
ICredentialDataDecryptedObject,
IDataObject, IDataObject,
IExecuteData, IExecuteData,
IExecuteWorkflowInfo, IExecuteWorkflowInfo,
@ -372,40 +370,6 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
} }
/**
* Updates credentials with new data
*
* @export
* @param {string} name Name of the credentials to update
* @param {string} type Type of the credentials to update
* @param {ICredentialDataDecryptedObject} data The new credential data
* @param {string} encryptionKey The encryption key to use
* @returns {Promise<void>}
*/
export async function updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject, encryptionKey: string): Promise<void> {
const foundCredentials = await Db.collections.Credentials!.find({ name, type });
if (!foundCredentials.length) {
throw new Error(`Could not find credentials for type "${type}" with name "${name}".`);
}
const credentialsDb = foundCredentials[0];
// Encrypt the data
const credentials = new Credentials(credentialsDb.name, credentialsDb.type, credentialsDb.nodesAccess);
credentials.setData(data, encryptionKey);
const newCredentialsData = credentials.getDataToSave() as ICredentialsDb;
// Add special database related data
newCredentialsData.updatedAt = this.getCurrentDate();
// TODO: also add user automatically depending on who is logged in, if anybody is logged in
// Save the credentials in DB
await Db.collections.Credentials!.save(newCredentialsData);
}
/** /**
* Returns the base additional data without webhooks * Returns the base additional data without webhooks
* *
@ -428,11 +392,11 @@ export async function getBase(credentials: IWorkflowCredentials, currentNodePara
return { return {
credentials, credentials,
credentialsHelper: new CredentialsHelper(credentials, encryptionKey),
encryptionKey, encryptionKey,
executeWorkflow, executeWorkflow,
restApiUrl: urlBaseWebhook + config.get('endpoints.rest') as string, restApiUrl: urlBaseWebhook + config.get('endpoints.rest') as string,
timezone, timezone,
updateCredentials,
webhookBaseUrl, webhookBaseUrl,
webhookTestBaseUrl, webhookTestBaseUrl,
currentNodeParameters, currentNodeParameters,

View file

@ -1,4 +1,6 @@
export * from './CredentialsHelper';
export * from './CredentialTypes'; export * from './CredentialTypes';
export * from './CredentialsOverwrites';
export * from './Interfaces'; export * from './Interfaces';
export * from './LoadNodesAndCredentials'; export * from './LoadNodesAndCredentials';
export * from './NodeTypes'; export * from './NodeTypes';

View file

@ -1,25 +1,14 @@
import { import {
ICredentialDataDecryptedObject,
CredentialInformation, CredentialInformation,
ICredentialDataDecryptedObject,
ICredentials,
ICredentialsEncrypted, ICredentialsEncrypted,
ICredentialNodeAccess,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { enc, AES } from 'crypto-js'; import { enc, AES } from 'crypto-js';
export class Credentials implements ICredentialsEncrypted {
name: string;
type: string;
data: string | undefined;
nodesAccess: ICredentialNodeAccess[];
export class Credentials extends ICredentials {
constructor(name: string, type: string, nodesAccess: ICredentialNodeAccess[], data?: string) {
this.name = name;
this.type = type;
this.nodesAccess = nodesAccess;
this.data = data;
}
/** /**

View file

@ -1,5 +1,4 @@
import { import {
Credentials,
IHookFunctions, IHookFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
IResponseError, IResponseError,
@ -154,7 +153,7 @@ export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string
const name = node.credentials[credentialsType]; const name = node.credentials[credentialsType];
// Save the refreshed token // Save the refreshed token
await additionalData.updateCredentials(name, credentialsType, newCredentialsData, additionalData.encryptionKey); await additionalData.credentialsHelper.updateCredentials(name, credentialsType, newCredentialsData);
// Make the request again with the new token // Make the request again with the new token
const newRequestOptions = newToken.sign(requestOptions as clientOAuth2.RequestObject); const newRequestOptions = newToken.sign(requestOptions as clientOAuth2.RequestObject);
@ -244,20 +243,7 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
const name = node.credentials[type]; const name = node.credentials[type];
if (!additionalData.credentials[type]) { const decryptedDataObject = additionalData.credentialsHelper.getDecrypted(name, type);
throw new Error(`No credentials of type "${type}" exist.`);
}
if (!additionalData.credentials[type][name]) {
throw new Error(`No credentials with name "${name}" exist for type "${type}".`);
}
const credentialData = additionalData.credentials[type][name];
const credentials = new Credentials(name, type, credentialData.nodesAccess, credentialData.data);
const decryptedDataObject = credentials.getData(additionalData.encryptionKey, node.type);
if (decryptedDataObject === null) {
throw new Error('Could not get the credentials');
}
return decryptedDataObject; return decryptedDataObject;
} }
@ -423,7 +409,7 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
helpers: { helpers: {
prepareBinaryData, prepareBinaryData,
request: requestPromise, request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData); return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData);
}, },
returnJsonArray, returnJsonArray,
@ -477,7 +463,7 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
helpers: { helpers: {
prepareBinaryData, prepareBinaryData,
request: requestPromise, request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData); return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData);
}, },
returnJsonArray, returnJsonArray,
@ -558,7 +544,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
helpers: { helpers: {
prepareBinaryData, prepareBinaryData,
request: requestPromise, request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData); return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData);
}, },
returnJsonArray, returnJsonArray,
@ -640,7 +626,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
helpers: { helpers: {
prepareBinaryData, prepareBinaryData,
request: requestPromise, request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData); return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData);
}, },
}, },
@ -690,7 +676,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
}, },
helpers: { helpers: {
request: requestPromise, request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData); return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData);
}, },
}, },
@ -748,7 +734,7 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
}, },
helpers: { helpers: {
request: requestPromise, request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData); return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData);
}, },
}, },
@ -833,7 +819,7 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
helpers: { helpers: {
prepareBinaryData, prepareBinaryData,
request: requestPromise, request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData); return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData);
}, },
returnJsonArray, returnJsonArray,

View file

@ -143,7 +143,6 @@ export async function writeUserSettings(userSettings: IUserSettings, settingsPat
*/ */
export async function getUserSettings(settingsPath?: string, ignoreCache?: boolean): Promise<IUserSettings | undefined> { export async function getUserSettings(settingsPath?: string, ignoreCache?: boolean): Promise<IUserSettings | undefined> {
if (settingsCache !== undefined && ignoreCache !== true) { if (settingsCache !== undefined && ignoreCache !== true) {
return settingsCache; return settingsCache;
} }

View file

@ -2,6 +2,7 @@ import { set } from 'lodash';
import { import {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
ICredentialsHelper,
IExecuteWorkflowInfo, IExecuteWorkflowInfo,
INodeExecutionData, INodeExecutionData,
INodeParameters, INodeParameters,
@ -16,11 +17,25 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
Credentials,
IDeferredPromise, IDeferredPromise,
IExecuteFunctions, IExecuteFunctions,
} from '../src'; } from '../src';
export class CredentialsHelper extends ICredentialsHelper {
getDecrypted(name: string, type: string): ICredentialDataDecryptedObject {
return {};
}
getCredentials(name: string, type: string): Credentials {
return new Credentials('', '', [], '');
}
async updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void> {}
}
class NodeTypesClass implements INodeTypes { class NodeTypesClass implements INodeTypes {
nodeTypes: INodeTypeData = { nodeTypes: INodeTypeData = {
@ -276,12 +291,12 @@ export function WorkflowExecuteAdditionalData(waitPromise: IDeferredPromise<IRun
return { return {
credentials: {}, credentials: {},
credentialsHelper: new CredentialsHelper({}, ''),
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData), hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData),
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {}, // tslint:disable-line:no-any executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {}, // tslint:disable-line:no-any
restApiUrl: '', restApiUrl: '',
encryptionKey: 'test', encryptionKey: 'test',
timezone: 'America/New_York', timezone: 'America/New_York',
updateCredentials: async (name: string, type: string, data: ICredentialDataDecryptedObject, encryptionKey: string): Promise<void> => {},
webhookBaseUrl: 'webhook', webhookBaseUrl: 'webhook',
webhookTestBaseUrl: 'webhook-test', webhookTestBaseUrl: 'webhook-test',
}; };

View file

@ -8,7 +8,7 @@
Credential type: Credential type:
</el-col> </el-col>
<el-col :span="18"> <el-col :span="18">
<el-select v-model="credentialType" placeholder="Select Type" size="small"> <el-select v-model="credentialType" filterable placeholder="Select Type" size="small">
<el-option <el-option
v-for="item in credentialTypes" v-for="item in credentialTypes"
:key="item.name" :key="item.name"

View file

@ -34,14 +34,14 @@
</el-row> </el-row>
<br /> <br />
<div class="headline"> <div class="headline" v-if="credentialProperties.length">
Credential Data: Credential Data:
<el-tooltip class="credentials-info" placement="top" effect="light"> <el-tooltip class="credentials-info" placement="top" effect="light">
<div slot="content" v-html="helpTexts.credentialsData"></div> <div slot="content" v-html="helpTexts.credentialsData"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</el-tooltip> </el-tooltip>
</div> </div>
<el-row v-for="parameter in credentialTypeData.properties" :key="parameter.name" class="parameter-wrapper"> <el-row v-for="parameter in credentialProperties" :key="parameter.name" class="parameter-wrapper">
<el-col :span="6" class="parameter-name"> <el-col :span="6" class="parameter-name">
{{parameter.displayName}}: {{parameter.displayName}}:
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light"> <el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
@ -97,7 +97,11 @@ import { restApi } from '@/components/mixins/restApi';
import { nodeHelpers } from '@/components/mixins/nodeHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { showMessage } from '@/components/mixins/showMessage'; import { showMessage } from '@/components/mixins/showMessage';
import { ICredentialsDecryptedResponse, IUpdateInformation } from '@/Interface'; import {
ICredentialsDecryptedResponse,
ICredentialsResponse,
IUpdateInformation,
} from '@/Interface';
import { import {
CredentialInformation, CredentialInformation,
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
@ -105,6 +109,7 @@ import {
ICredentialType, ICredentialType,
ICredentialNodeAccess, ICredentialNodeAccess,
INodeCredentialDescription, INodeCredentialDescription,
INodeProperties,
INodeTypeDescription, INodeTypeDescription,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -172,15 +177,20 @@ export default mixins(
}; };
}); });
}, },
credentialProperties (): INodeProperties[] {
return this.credentialTypeData.properties.filter((propertyData: INodeProperties) => {
return !this.credentialTypeData.__overwrittenProperties || !this.credentialTypeData.__overwrittenProperties.includes(propertyData.name);
});
},
isOAuthType (): boolean { isOAuthType (): boolean {
return this.credentialData && this.credentialData.type === 'oAuth2Api'; return this.credentialTypeData.name === 'oAuth2Api' || (this.credentialTypeData.extends !== undefined && this.credentialTypeData.extends.includes('oAuth2Api'));
}, },
isOAuthConnected (): boolean { isOAuthConnected (): boolean {
if (this.isOAuthType === false) { if (this.isOAuthType === false) {
return false; return false;
} }
return !!this.credentialData.data.oauthTokenData; return this.credentialData !== null && !!this.credentialData.data.oauthTokenData;
}, },
}, },
methods: { methods: {
@ -192,7 +202,7 @@ export default mixins(
tempValue[name] = parameterData.value; tempValue[name] = parameterData.value;
Vue.set(this, 'propertyValue', tempValue); Vue.set(this, 'propertyValue', tempValue);
}, },
async createCredentials (): Promise<void> { async createCredentials (doNotEmitData?: boolean): Promise<ICredentialsResponse | null> {
const nodesAccess = this.nodesAccess.map((nodeType) => { const nodesAccess = this.nodesAccess.map((nodeType) => {
return { return {
nodeType, nodeType,
@ -211,18 +221,35 @@ export default mixins(
result = await this.restApi().createNewCredentials(newCredentials); result = await this.restApi().createNewCredentials(newCredentials);
} catch (error) { } catch (error) {
this.$showError(error, 'Problem Creating Credentials', 'There was a problem creating the credentials:'); this.$showError(error, 'Problem Creating Credentials', 'There was a problem creating the credentials:');
return; return null;
} }
// Add also to local store // Add also to local store
this.$store.commit('addCredentials', result); this.$store.commit('addCredentials', result);
if (doNotEmitData !== true) {
this.$emit('credentialsCreated', result); this.$emit('credentialsCreated', result);
}
return result;
}, },
async oAuth2CredentialAuthorize () { async oAuth2CredentialAuthorize () {
let url; let url;
let credentialData = this.credentialData;
let newCredentials = false;
if (!credentialData) {
// Credentials did not get created yet. So create first before
// doing oauth authorize
credentialData = await this.createCredentials(true);
newCredentials = true;
if (credentialData === null) {
return;
}
}
try { try {
url = await this.restApi().oAuth2CredentialAuthorize(this.credentialData) as string; url = await this.restApi().oAuth2CredentialAuthorize(credentialData) as string;
} catch (error) { } catch (error) {
this.$showError(error, 'OAuth Authorization Error', 'Error generating authorization URL:'); this.$showError(error, 'OAuth Authorization Error', 'Error generating authorization URL:');
return; return;
@ -241,13 +268,17 @@ export default mixins(
// Set some kind of data that status changes. // Set some kind of data that status changes.
// As data does not get displayed directly it does not matter what data. // As data does not get displayed directly it does not matter what data.
this.credentialData.data.oauthTokenData = {}; credentialData.data.oauthTokenData = {};
// Close the window // Close the window
if (oauthPopup) { if (oauthPopup) {
oauthPopup.close(); oauthPopup.close();
} }
if (newCredentials === true) {
this.$emit('credentialsCreated', credentialData);
}
this.$showMessage({ this.$showMessage({
title: 'Connected', title: 'Connected',
message: 'Got connected!', message: 'Got connected!',

View file

@ -35,6 +35,27 @@ export interface IGetCredentials {
get(type: string, name: string): Promise<ICredentialsEncrypted>; get(type: string, name: string): Promise<ICredentialsEncrypted>;
} }
export abstract class ICredentials {
name: string;
type: string;
data: string | undefined;
nodesAccess: ICredentialNodeAccess[];
constructor(name: string, type: string, nodesAccess: ICredentialNodeAccess[], data?: string) {
this.name = name;
this.type = type;
this.nodesAccess = nodesAccess;
this.data = data;
}
abstract getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject;
abstract getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation;
abstract getDataToSave(): ICredentialsEncrypted;
abstract hasNodeAccess(nodeType: string): boolean;
abstract setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void;
abstract setDataKey(key: string, data: CredentialInformation, encryptionKey: string): void;
}
// Defines which nodes are allowed to access the credentials and // Defines which nodes are allowed to access the credentials and
// when that access got grented from which user // when that access got grented from which user
export interface ICredentialNodeAccess { export interface ICredentialNodeAccess {
@ -57,11 +78,26 @@ export interface ICredentialsEncrypted {
data?: string; data?: string;
} }
export abstract class ICredentialsHelper {
encryptionKey: string;
workflowCredentials: IWorkflowCredentials;
constructor(workflowCredentials: IWorkflowCredentials, encryptionKey: string) {
this.encryptionKey = encryptionKey;
this.workflowCredentials = workflowCredentials;
}
abstract getCredentials(name: string, type: string): ICredentials;
abstract getDecrypted(name: string, type: string): ICredentialDataDecryptedObject;
abstract updateCredentials(name: string, type: string, data: ICredentialDataDecryptedObject): Promise<void>;
}
export interface ICredentialType { export interface ICredentialType {
name: string; name: string;
displayName: string; displayName: string;
extends?: string[]; extends?: string[];
properties: INodeProperties[]; properties: INodeProperties[];
__overwrittenProperties?: string[];
} }
export interface ICredentialTypes { export interface ICredentialTypes {
@ -644,6 +680,7 @@ export interface IWorkflowExecuteHooks {
export interface IWorkflowExecuteAdditionalData { export interface IWorkflowExecuteAdditionalData {
credentials: IWorkflowCredentials; credentials: IWorkflowCredentials;
credentialsHelper: ICredentialsHelper;
encryptionKey: string; encryptionKey: string;
executeWorkflow: (workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[]) => Promise<any>; // tslint:disable-line:no-any executeWorkflow: (workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[]) => Promise<any>; // tslint:disable-line:no-any
// hooks?: IWorkflowExecuteHooks; // hooks?: IWorkflowExecuteHooks;
@ -652,7 +689,6 @@ export interface IWorkflowExecuteAdditionalData {
httpRequest?: express.Request; httpRequest?: express.Request;
restApiUrl: string; restApiUrl: string;
timezone: string; timezone: string;
updateCredentials: (name: string, type: string, data: ICredentialDataDecryptedObject, encryptionKey: string) => Promise<void>;
webhookBaseUrl: string; webhookBaseUrl: string;
webhookTestBaseUrl: string; webhookTestBaseUrl: string;
currentNodeParameters? : INodeParameters[]; currentNodeParameters? : INodeParameters[];