Merge branch 'master' into static-stateless-webhooks

This commit is contained in:
ricardo
2020-06-22 16:16:50 -04:00
217 changed files with 32466 additions and 2487 deletions

View File

@@ -1,12 +1,13 @@
import {
Credentials,
IHookFunctions,
ILoadOptionsFunctions,
IResponseError,
IWorkflowSettings,
BINARY_ENCODING,
} from './';
import {
IAllExecuteFunctions,
IBinaryData,
IContextObject,
ICredentialDataDecryptedObject,
@@ -35,13 +36,20 @@ import {
WorkflowExecuteMode,
} from 'n8n-workflow';
import * as clientOAuth1 from 'oauth-1.0a';
import { RequestOptions, Token } from 'oauth-1.0a';
import * as clientOAuth2 from 'client-oauth2';
import { get } from 'lodash';
import * as express from "express";
import * as express from 'express';
import * as path from 'path';
import { OptionsWithUrl, OptionsWithUri } from 'request';
import * as requestPromise from 'request-promise-native';
import { Magic, MAGIC_MIME_TYPE } from 'mmmagic';
import { createHmac } from 'crypto';
const magic = new Magic(MAGIC_MIME_TYPE);
@@ -102,6 +110,135 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m
/**
* Makes a request using OAuth data for authentication
*
* @export
* @param {IAllExecuteFunctions} this
* @param {string} credentialsType
* @param {(OptionsWithUri | requestPromise.RequestPromiseOptions)} requestOptions
* @param {INode} node
* @param {IWorkflowExecuteAdditionalData} additionalData
* @returns
*/
export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string, property?: string) {
const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
if (credentials.oauthTokenData === undefined) {
throw new Error('OAuth credentials not connected!');
}
const oAuthClient = new clientOAuth2({
clientId: credentials.clientId as string,
clientSecret: credentials.clientSecret as string,
accessTokenUri: credentials.accessTokenUrl as string,
});
const oauthTokenData = credentials.oauthTokenData as clientOAuth2.Data;
const token = oAuthClient.createToken(get(oauthTokenData, property as string) || oauthTokenData.accessToken, oauthTokenData.refreshToken, tokenType || oauthTokenData.tokenType, oauthTokenData);
// Signs the request by adding authorization headers or query parameters depending
// on the token-type used.
const newRequestOptions = token.sign(requestOptions as clientOAuth2.RequestObject);
return this.helpers.request!(newRequestOptions)
.catch(async (error: IResponseError) => {
// TODO: Check if also other codes are possible
if (error.statusCode === 401) {
// TODO: Whole refresh process is not tested yet
// Token is probably not valid anymore. So try refresh it.
const newToken = await token.refresh();
credentials.oauthTokenData = newToken.data;
// Find the name of the credentials
if (!node.credentials || !node.credentials[credentialsType]) {
throw new Error(`The node "${node.name}" does not have credentials of type "${credentialsType}"!`);
}
const name = node.credentials[credentialsType];
// Save the refreshed token
await additionalData.credentialsHelper.updateCredentials(name, credentialsType, credentials);
// Make the request again with the new token
const newRequestOptions = newToken.sign(requestOptions as clientOAuth2.RequestObject);
return this.helpers.request!(newRequestOptions);
}
// Unknown error so simply throw it
throw error;
});
}
/* Makes a request using OAuth1 data for authentication
*
* @export
* @param {IAllExecuteFunctions} this
* @param {string} credentialsType
* @param {(OptionsWithUrl | requestPromise.RequestPromiseOptions)} requestOptionså
* @returns
*/
export function requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions) {
const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
if (credentials.oauthTokenData === undefined) {
throw new Error('OAuth credentials not connected!');
}
const oauth = new clientOAuth1({
consumer: {
key: credentials.consumerKey as string,
secret: credentials.consumerSecret as string,
},
signature_method: credentials.signatureMethod as string,
hash_function(base, key) {
const algorithm = (credentials.signatureMethod === 'HMAC-SHA1') ? 'sha1' : 'sha256';
return createHmac(algorithm, key)
.update(base)
.digest('base64');
},
});
const oauthTokenData = credentials.oauthTokenData as IDataObject;
const token: Token = {
key: oauthTokenData.oauth_token as string,
secret: oauthTokenData.oauth_token_secret as string,
};
const newRequestOptions = {
//@ts-ignore
url: requestOptions.url,
method: requestOptions.method,
data: { ...requestOptions.qs, ...requestOptions.body },
json: requestOptions.json,
};
if (Object.keys(requestOptions.qs).length !== 0) {
//@ts-ignore
newRequestOptions.qs = oauth.authorize(newRequestOptions as RequestOptions, token);
} else {
//@ts-ignore
newRequestOptions.form = oauth.authorize(newRequestOptions as RequestOptions, token);
}
return this.helpers.request!(newRequestOptions)
.catch(async (error: IResponseError) => {
// Unknown error so simply throw it
throw error;
});
}
/**
* Takes generic input data and brings it into the json format n8n uses.
*
@@ -177,20 +314,7 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
const name = node.credentials[type];
if (!additionalData.credentials[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');
}
const decryptedDataObject = additionalData.credentialsHelper.getDecrypted(name, type);
return decryptedDataObject;
}
@@ -406,6 +530,12 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
},
returnJsonArray,
},
};
@@ -463,6 +593,12 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
},
returnJsonArray,
},
};
@@ -553,6 +689,12 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
},
returnJsonArray,
},
};
@@ -645,6 +787,12 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
},
},
};
})(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex);
@@ -695,6 +843,12 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
},
helpers: {
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
},
},
};
return that;
@@ -756,6 +910,12 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
},
helpers: {
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
},
},
};
return that;
@@ -844,6 +1004,12 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
requestOAuth1(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions): Promise<any> { // tslint:disable-line:no-any
return requestOAuth1.call(this, credentialsType, requestOptions);
},
returnJsonArray,
},
};