mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-23 11:44:06 -08:00
✨ Nodes as JSON and authentication redesign (#2401)
* ✨ change FE to handle new object type * 🚸 improve UX of handling invalid credentials * 🚧 WIP * 🎨 fix typescript issues * 🐘 add migrations for all supported dbs * ✏️ add description to migrations * ⚡ add credential update on import * ⚡ resolve after merge issues * 👕 fix lint issues * ⚡ check credentials on workflow create/update * update interface * 👕 fix ts issues * ⚡ adaption to new credentials UI * 🐛 intialize cache on BE for credentials check * 🐛 fix undefined oldCredentials * 🐛 fix deleting credential * 🐛 fix check for undefined keys * 🐛 fix disabling edit in execution * 🎨 just show credential name on execution view * ✏️ remove TODO * ⚡ implement review suggestions * ⚡ add cache to getCredentialsByType * ⏪ use getter instead of cache * ✏️ fix variable name typo * 🐘 include waiting nodes to migrations * 🐛 fix reverting migrations command * ⚡ update typeorm command * ✨ create db:revert command * 👕 fix lint error * ✨ Add optional authenticate method to credentials * ⚡ Simplify code and add authentication support to MattermostApi * 👕 Fix lint issue * ⚡ Add support to own-mode * 👕 Fix lint issue * ✨ Add support for predefined auth types bearer and headerAuth * ⚡ Make sure that DateTime Node always returns strings * ⚡ Add support for moment types to If Node * ⚡ Make it possible for HTTP Request Node to use all credential types * ✨ Add basicAuth support * Add a new dropcontact node * ✨ First basic implementation of mainly JSON based nodes * ✨ Add fixedCollection support, added value parameter and expression support for value and property * Improvements to #2389 * ⚡ Add credentials verification * ⚡ Small improvement * ⚡ set default time to 45 seconds * ✨ Add support for preSend and postReceive methods * ➕ Add lodash merge and set depedency to workflow * 👕 Fix lint issue * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * 🐛 Set siren and language correctly * ⚡ Add support for requestDefaults * ⚡ Add support for baseURL to httpRequest * ⚡ Move baseURL to correct location * ✨ Add support for options loading * 🐛 Fix error with fullAccess nodes * ✨ Add credential test functionality * 🐛 Fix issue with OAuth autentication and lint issue * ⚡ Fix build issue * 🐛 Fix issue that url got always overwritten to empty * ✨ Add pagination support * ⚡ Code fix required after merge * ⚡ Remove not needed imports * ⚡ Fix credential test * ✨ Add expression support for request properties and $self support on properties * ⚡ Rename $self to $value * 👕 Fix lint issue * ⚡ Add example how to send data in path * ✨ Make it possible to not sent in dot notation * ✨ Add support for postReceive:rootProperty * ⚡ Fix typo * ✨ Add support for postReceive:set * ⚡ Some fixes * ⚡ Small improvement * ;zap: Separate RoutingNode code * ⚡ Simplify code and fix bug * ⚡ Remove unused code * ✨ Make it possible to define "request" and "requestProperty" on options * 👕 Fix lint issue * ⚡ Change $credentials variables name * ✨ Enable expressions and access to credentials in requestDefaults * ⚡ Make parameter option loading use RoutingNode.makeRoutingRequest * ✨ Allow requestOperations overwrite on LoadOptions * ✨ Make it possible to access current node parameters in loadOptions * ⚡ Rename parameters variable to make future proof * ⚡ Make it possible to use offset-pagination with body * ✨ Add support for queryAuth * ⚡ Never return more items than requested * ✨ Make it possible to overwrite requestOperations on parameter and option level * 👕 Fix lint issue * ✨ Allow simplified auth also with regular nodes * ✨ Add support for receiving binary data * 🐛 Fix example node * ⚡ Rename property "name" to "displayName" in loadOptions * ⚡ Send data by default as "query" if nothing is set * ⚡ Rename $self to $parent * ⚡ Change to work with INodeExecutionData instead of IDataObject * ⚡ Improve binaryData handling * ⚡ Property design improvements * ⚡ Fix property name * 🚨 Add some tests * ⚡ Add also test for request * ⚡ Improve test and fix issues * ⚡ Improvements to loadOptions * ⚡ Normalize loadOptions with rest of code * ⚡ Add info text * ✨ Add support for $value in postReceive * 🚨 Add tests for RoutingNode.runNode * ⚡ Remove TODOs and make url property optional * ⚡ Fix bug and lint issue * 🐛 Fix bug that not the correct property got used * 🚨 Add tests for CredentialsHelper.authenticate * ⚡ Improve code and resolve expressions also everywhere for loadOptions and credential test requests * ✨ Make it possible to define multiple preSend and postReceive actions * ✨ Allow to define tests on credentials * ⚡ Remove test data * ⬆️ Update package-lock.json file * ⚡ Remove old not longer used code Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com> Co-authored-by: Mutasem <mutdmour@gmail.com> Co-authored-by: PaulineDropcontact <pauline@dropcontact.io> Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
This commit is contained in:
parent
f23098e38b
commit
0da398b0e4
77
package-lock.json
generated
77
package-lock.json
generated
|
@ -4,12 +4,12 @@
|
|||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.0.2.tgz",
|
||||
"integrity": "sha512-sE8Gx+qSDMLoJvb3QarJJlDQK7SSY4rK3hxp4XsiANeFOmjU46ZI7Y9adAQRJrmbz8zbtZkp3mJTT+rGxtF0XA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.0.3.tgz",
|
||||
"integrity": "sha512-DmIAguV77yFP0MGVFWknCMgSLAtsLR3VlRTteR6xgMpIfYtwaZuMvjGv5YlpiqN7S/5q87DHyuIx8oa15kiyag==",
|
||||
"requires": {
|
||||
"@jridgewell/trace-mapping": "^0.2.2",
|
||||
"sourcemap-codec": "1.4.8"
|
||||
"@jridgewell/sourcemap-codec": "^1.4.9",
|
||||
"@jridgewell/trace-mapping": "^0.2.7"
|
||||
}
|
||||
},
|
||||
"@azure/abort-controller": {
|
||||
|
@ -1935,9 +1935,9 @@
|
|||
}
|
||||
},
|
||||
"@fontsource/open-sans": {
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.5.3.tgz",
|
||||
"integrity": "sha512-zabYpvz2XkZ4Vp1EN2/k0r5X9kQgwjdj1+kJ6B0T/oN4h9yqJqr9VKxa+JspRxClxDEo23K5GqfuIEH1+WyFOw=="
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.5.4.tgz",
|
||||
"integrity": "sha512-iaEuU7l3VGA/bqWW9UsBD2bgFwCwDFwKlmOUft4Jps3pD3Zc9POMNYV0+mNyKbA4OIcIice32l+BMif8vY6pdg=="
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "0.2.36",
|
||||
|
@ -4049,13 +4049,18 @@
|
|||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz",
|
||||
"integrity": "sha512-cz8HFjOFfUBtvN+NXYSFMHYRdxZMaEl0XypVrhzxBgadKIXhIkRd8aMeHhmF56Sl7SuS8OnUpQ73/k9LE4VnLg=="
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.10.tgz",
|
||||
"integrity": "sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg=="
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.2.6.tgz",
|
||||
"integrity": "sha512-rVJf5dSMEBxnDEwtAT5x8+p6tZ+xU6Ocm+cR1MYL2gMsRi4MMzVf9Pvq6JaxIsEeKAyYmo2U+yPQN4QfdTfFnA==",
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.2.7.tgz",
|
||||
"integrity": "sha512-ZKfRhw6eK2vvdWqpU7DQq49+BZESqh5rmkYpNhuzkz01tapssl2sNNy6uMUIgrTtUWQDijomWJzJRCoevVrfgw==",
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"sourcemap-codec": "1.4.8"
|
||||
"@jridgewell/sourcemap-codec": "^1.4.9"
|
||||
}
|
||||
},
|
||||
"@kafkajs/confluent-schema-registry": {
|
||||
|
@ -12292,9 +12297,9 @@
|
|||
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w=="
|
||||
},
|
||||
"@types/cheerio": {
|
||||
"version": "0.22.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.30.tgz",
|
||||
"integrity": "sha512-t7ZVArWZlq3dFa9Yt33qFBQIK4CQd1Q3UJp0V+UhP6vgLWLM6Qug7vZuRSGXg45zXeB1Fm5X2vmBkEX58LV2Tw==",
|
||||
"version": "0.22.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz",
|
||||
"integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
|
@ -12602,6 +12607,14 @@
|
|||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash.merge": {
|
||||
"version": "4.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.6.tgz",
|
||||
"integrity": "sha512-IB90krzMf7YpfgP3u/EvZEdXVvm4e3gJbUvh5ieuI+o+XqiNEt6fCzqNRaiLlPVScLI59RxIGZMQ3+Ko/DJ8vQ==",
|
||||
"requires": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash.set": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.set/-/lodash.set-4.3.6.tgz",
|
||||
|
@ -15858,9 +15871,9 @@
|
|||
"integrity": "sha512-uUbetCWczQHbsKyX1C99XpQHBM8SWfovvaZhPIj23/1uV7SQf0WeRZbiLpw0JZm+LHTChfNgrLfDJOVoU2kU+A=="
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.1068.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1068.0.tgz",
|
||||
"integrity": "sha512-lD8JaEVSDueoTdhwxYinkZKuCzsqCE1L6+NZhO1AVdwgtK62pzjU20VHX2K39+Y2XebeAO2QzD+32m0ROHAeZg==",
|
||||
"version": "2.1069.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1069.0.tgz",
|
||||
"integrity": "sha512-AF7/5JotrVd8g/D3WWHgQto+IryB1V7iudIYm+H+qxmkGOU3xvL63ChhEoLTY/CxuK/diayg0oWILEsXUn3dfw==",
|
||||
"requires": {
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
|
@ -21284,9 +21297,9 @@
|
|||
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.64",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.64.tgz",
|
||||
"integrity": "sha512-8mec/99xgLUZCIZZq3wt61Tpxg55jnOSpxGYapE/1Ma9MpFEYYaz4QNYm0CM1rrnCo7i3FRHhbaWjeCLsveGjQ=="
|
||||
"version": "1.4.65",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.65.tgz",
|
||||
"integrity": "sha512-0/d8Skk8sW3FxXP0Dd6MnBlrwx7Qo9cqQec3BlIAlvKnrmS3pHsIbaroEi+nd0kZkGpQ6apMEre7xndzjlEnLw=="
|
||||
},
|
||||
"element-resize-detector": {
|
||||
"version": "1.2.4",
|
||||
|
@ -27805,9 +27818,9 @@
|
|||
}
|
||||
},
|
||||
"istanbul-reports": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz",
|
||||
"integrity": "sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg==",
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz",
|
||||
"integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==",
|
||||
"requires": {
|
||||
"html-escaper": "^2.0.0",
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
|
@ -40357,9 +40370,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
||||
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz",
|
||||
"integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
|
@ -43624,13 +43637,13 @@
|
|||
}
|
||||
},
|
||||
"winston-transport": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.2.tgz",
|
||||
"integrity": "sha512-9jmhltAr5ygt5usgUTQbEiw/7RYXpyUbEAFRCSicIacpUzPkrnQsQZSPGEI12aLK9Jth4zNcYJx3Cvznwrl8pw==",
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz",
|
||||
"integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==",
|
||||
"requires": {
|
||||
"logform": "^2.3.2",
|
||||
"readable-stream": "^3.4.0",
|
||||
"triple-beam": "^1.2.0"
|
||||
"readable-stream": "^3.6.0",
|
||||
"triple-beam": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"with": {
|
||||
|
|
|
@ -102,6 +102,7 @@ export class Execute extends Command {
|
|||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
workflowId = workflowData.id ? workflowData.id.toString() : PLACEHOLDER_EMPTY_WORKFLOW_ID;
|
||||
}
|
||||
|
||||
|
|
|
@ -66,11 +66,13 @@
|
|||
"@types/jest": "^26.0.13",
|
||||
"@types/localtunnel": "^1.9.0",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.merge": "^4.6.6",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/open": "^6.1.0",
|
||||
"@types/parseurl": "^1.3.1",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"@types/validator": "^13.7.0",
|
||||
"axios": "^0.21.1",
|
||||
"concurrently": "^5.1.0",
|
||||
"jest": "^26.4.2",
|
||||
"nodemon": "^2.0.2",
|
||||
|
@ -110,6 +112,7 @@
|
|||
"jwks-rsa": "~1.12.1",
|
||||
"localtunnel": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.103.0",
|
||||
"n8n-editor-ui": "~0.128.0",
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import { ICredentialType, ICredentialTypes as ICredentialTypesInterface } from 'n8n-workflow';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { ICredentialsTypeData } from '.';
|
||||
import {
|
||||
ICredentialType,
|
||||
ICredentialTypeData,
|
||||
ICredentialTypes as ICredentialTypesInterface,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
class CredentialTypesClass implements ICredentialTypesInterface {
|
||||
credentialTypes: ICredentialsTypeData = {};
|
||||
credentialTypes: ICredentialTypeData = {};
|
||||
|
||||
async init(credentialTypes: ICredentialsTypeData): Promise<void> {
|
||||
async init(credentialTypes: ICredentialTypeData): Promise<void> {
|
||||
this.credentialTypes = credentialTypes;
|
||||
}
|
||||
|
||||
getAll(): ICredentialType[] {
|
||||
return Object.values(this.credentialTypes);
|
||||
return Object.values(this.credentialTypes).map((data) => data.type);
|
||||
}
|
||||
|
||||
getByName(credentialType: string): ICredentialType {
|
||||
return this.credentialTypes[credentialType];
|
||||
return this.credentialTypes[credentialType].type;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,42 +1,217 @@
|
|||
import { Credentials } from 'n8n-core';
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
|
||||
import { Credentials, NodeExecuteFunctions } from 'n8n-core';
|
||||
|
||||
import { NodeVersionedType } from 'n8n-nodes-base';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialsExpressionResolveValues,
|
||||
ICredentialsHelper,
|
||||
ICredentialTestFunction,
|
||||
ICredentialTestRequestData,
|
||||
IHttpRequestOptions,
|
||||
INode,
|
||||
INodeCredentialsDetails,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
INodeVersionedType,
|
||||
IRequestOptionsSimplified,
|
||||
IRunExecutionData,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
NodeHelpers,
|
||||
RoutingNode,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
ITaskDataConnections,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { CredentialsOverwrites, CredentialTypes, Db, ICredentialsDb } from '.';
|
||||
import {
|
||||
CredentialsOverwrites,
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ICredentialsDb,
|
||||
NodeTypes,
|
||||
WorkflowExecuteAdditionalData,
|
||||
} from '.';
|
||||
|
||||
const mockNodeTypes: INodeTypes = {
|
||||
nodeTypes: {},
|
||||
nodeTypes: {} as INodeTypeData,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
init: async (nodeTypes?: INodeTypeData): Promise<void> => {},
|
||||
getAll: (): INodeType[] => {
|
||||
// Does not get used in Workflow so no need to return it
|
||||
return [];
|
||||
getAll(): Array<INodeType | INodeVersionedType> {
|
||||
// @ts-ignore
|
||||
return Object.values(this.nodeTypes).map((data) => data.type);
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getByName: (nodeType: string): INodeType | undefined => {
|
||||
return undefined;
|
||||
getByName(nodeType: string): INodeType | INodeVersionedType | undefined {
|
||||
if (this.nodeTypes[nodeType] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return this.nodeTypes[nodeType].type;
|
||||
},
|
||||
getByNameAndVersion: (): INodeType | undefined => {
|
||||
return undefined;
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType | undefined {
|
||||
if (this.nodeTypes[nodeType] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
||||
},
|
||||
};
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
private credentialTypes = CredentialTypes();
|
||||
|
||||
/**
|
||||
* Add the required authentication information to the request
|
||||
*/
|
||||
async authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
incomingRequestOptions: IHttpRequestOptions | IRequestOptionsSimplified,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
const requestOptions = incomingRequestOptions;
|
||||
const credentialType = this.credentialTypes.getByName(typeName);
|
||||
|
||||
if (credentialType.authenticate) {
|
||||
if (typeof credentialType.authenticate === 'function') {
|
||||
// Special authentication function is defined
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
return credentialType.authenticate(credentials, requestOptions as IHttpRequestOptions);
|
||||
}
|
||||
|
||||
if (typeof credentialType.authenticate === 'object') {
|
||||
// Predefined authentication method
|
||||
|
||||
const { authenticate } = credentialType;
|
||||
if (requestOptions.headers === undefined) {
|
||||
requestOptions.headers = {};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
if (authenticate.type === 'bearer') {
|
||||
const tokenPropertyName: string =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
authenticate.properties.tokenPropertyName ?? 'accessToken';
|
||||
requestOptions.headers.Authorization = `Bearer ${
|
||||
credentials[tokenPropertyName] as string
|
||||
}`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
} else if (authenticate.type === 'basicAuth') {
|
||||
const userPropertyName: string =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
authenticate.properties.userPropertyName ?? 'user';
|
||||
const passwordPropertyName: string =
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
authenticate.properties.passwordPropertyName ?? 'password';
|
||||
|
||||
requestOptions.auth = {
|
||||
username: credentials[userPropertyName] as string,
|
||||
password: credentials[passwordPropertyName] as string,
|
||||
};
|
||||
} else if (authenticate.type === 'headerAuth') {
|
||||
const key = this.resolveValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
authenticate.properties.name,
|
||||
{ $credentials: credentials },
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
|
||||
const value = this.resolveValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
authenticate.properties.value,
|
||||
{ $credentials: credentials },
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
requestOptions.headers[key] = value;
|
||||
} else if (authenticate.type === 'queryAuth') {
|
||||
const key = this.resolveValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
authenticate.properties.key,
|
||||
{ $credentials: credentials },
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
|
||||
const value = this.resolveValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
authenticate.properties.value,
|
||||
{ $credentials: credentials },
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
if (!requestOptions.qs) {
|
||||
requestOptions.qs = {};
|
||||
}
|
||||
requestOptions.qs[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestOptions as IHttpRequestOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given value in case it is an expression
|
||||
*/
|
||||
resolveValue(
|
||||
parameterValue: string,
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
): string {
|
||||
if (parameterValue.charAt(0) !== '=') {
|
||||
return parameterValue;
|
||||
}
|
||||
|
||||
const returnValue = workflow.expression.getSimpleParameterValue(
|
||||
node,
|
||||
parameterValue,
|
||||
'internal',
|
||||
additionalKeys,
|
||||
'',
|
||||
);
|
||||
|
||||
if (!returnValue) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return returnValue.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all parent types of the given credential type
|
||||
*/
|
||||
getParentTypes(typeName: string): string[] {
|
||||
const credentialType = this.credentialTypes.getByName(typeName);
|
||||
|
||||
if (credentialType === undefined || credentialType.extends === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let types: string[] = [];
|
||||
credentialType.extends.forEach((type: string) => {
|
||||
types = [...types, typeName, ...this.getParentTypes(type)];
|
||||
});
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the credentials instance
|
||||
*
|
||||
|
@ -77,8 +252,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
* @memberof CredentialsHelper
|
||||
*/
|
||||
getCredentialsProperties(type: string): INodeProperties[] {
|
||||
const credentialTypes = CredentialTypes();
|
||||
const credentialTypeData = credentialTypes.getByName(type);
|
||||
const credentialTypeData = this.credentialTypes.getByName(type);
|
||||
|
||||
if (credentialTypeData === undefined) {
|
||||
throw new Error(`The credentials of type "${type}" are not known.`);
|
||||
|
@ -89,7 +263,6 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
}
|
||||
|
||||
const combineProperties = [] as INodeProperties[];
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const credentialsTypeName of credentialTypeData.extends) {
|
||||
const mergeCredentialProperties = this.getCredentialsProperties(credentialsTypeName);
|
||||
NodeHelpers.mergeNodeProperties(combineProperties, mergeCredentialProperties);
|
||||
|
@ -260,4 +433,229 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
await Db.collections.Credentials!.update(findQuery, newCredentialsData);
|
||||
}
|
||||
|
||||
getCredentialTestFunction(
|
||||
credentialType: string,
|
||||
nodeToTestWith?: string,
|
||||
): ICredentialTestFunction | ICredentialTestRequestData | undefined {
|
||||
const nodeTypes = NodeTypes();
|
||||
const allNodes = nodeTypes.getAll();
|
||||
|
||||
// Check all the nodes one by one if they have a test function defined
|
||||
for (let i = 0; i < allNodes.length; i++) {
|
||||
const node = allNodes[i];
|
||||
|
||||
if (nodeToTestWith && node.description.name !== nodeToTestWith) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
// Always set to an array even if node is not versioned to not having
|
||||
// to duplicate the logic
|
||||
const allNodeTypes: INodeType[] = [];
|
||||
if (node instanceof NodeVersionedType) {
|
||||
// Node is versioned
|
||||
allNodeTypes.push(...Object.values((node as INodeVersionedType).nodeVersions));
|
||||
} else {
|
||||
// Node is not versioned
|
||||
allNodeTypes.push(node as INodeType);
|
||||
}
|
||||
|
||||
// Check each of the node versions for credential tests
|
||||
for (const nodeType of allNodeTypes) {
|
||||
// Check each of teh credentials
|
||||
for (const credential of nodeType.description.credentials ?? []) {
|
||||
if (credential.name === credentialType && !!credential.testedBy) {
|
||||
if (typeof credential.testedBy === 'string') {
|
||||
// Test is defined as string which links to a functoin
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return (node as unknown as INodeType).methods?.credentialTest![credential.testedBy];
|
||||
}
|
||||
|
||||
// Test is defined as JSON with a defintion for the request to make
|
||||
return {
|
||||
nodeType,
|
||||
testRequest: credential.testedBy,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if test is defined on credentials
|
||||
const type = this.credentialTypes.getByName(credentialType);
|
||||
if (type.test) {
|
||||
return {
|
||||
testRequest: type.test,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async testCredentials(
|
||||
credentialType: string,
|
||||
credentialsDecrypted: ICredentialsDecrypted,
|
||||
nodeToTestWith?: string,
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const credentialTestFunction = this.getCredentialTestFunction(credentialType, nodeToTestWith);
|
||||
|
||||
if (credentialTestFunction === undefined) {
|
||||
return Promise.resolve({
|
||||
status: 'Error',
|
||||
message: 'No testing function found for this credential.',
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof credentialTestFunction === 'function') {
|
||||
// The credentials get tested via a function that is defined on the node
|
||||
const credentialTestFunctions = NodeExecuteFunctions.getCredentialTestFunctions();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
return credentialTestFunction.call(credentialTestFunctions, credentialsDecrypted);
|
||||
}
|
||||
|
||||
// Credentials get tested via request instructions
|
||||
|
||||
// TODO: Temp worfklows get created at multiple locations (for example also LoadNodeParameterOptions),
|
||||
// check if some of them are identical enough that it can be combined
|
||||
|
||||
let nodeType: INodeType;
|
||||
if (credentialTestFunction.nodeType) {
|
||||
nodeType = credentialTestFunction.nodeType;
|
||||
} else {
|
||||
const nodeTypes = NodeTypes();
|
||||
nodeType = nodeTypes.getByName('n8n-nodes-base.noOp') as INodeType;
|
||||
}
|
||||
|
||||
const node: INode = {
|
||||
parameters: {},
|
||||
name: 'Temp-Node',
|
||||
type: nodeType.description.name,
|
||||
typeVersion: nodeType.description.version,
|
||||
position: [0, 0],
|
||||
};
|
||||
|
||||
const workflowData = {
|
||||
nodes: [node],
|
||||
connections: {},
|
||||
};
|
||||
|
||||
const nodeTypeCopy: INodeType = {
|
||||
description: {
|
||||
...nodeType.description,
|
||||
credentials: [
|
||||
{
|
||||
name: credentialType,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Temp',
|
||||
name: 'temp',
|
||||
type: 'string',
|
||||
routing: {
|
||||
request: credentialTestFunction.testRequest.request,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const nodeTypes: INodeTypes = {
|
||||
...mockNodeTypes,
|
||||
nodeTypes: {
|
||||
[nodeTypeCopy.description.name]: {
|
||||
sourcePath: '',
|
||||
type: nodeTypeCopy,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const workflow = new Workflow({
|
||||
nodes: workflowData.nodes,
|
||||
connections: workflowData.connections,
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const mode = 'internal';
|
||||
const runIndex = 0;
|
||||
const inputData: ITaskDataConnections = {
|
||||
main: [[{ json: {} }]],
|
||||
};
|
||||
const connectionInputData: INodeExecutionData[] = [];
|
||||
const runExecutionData: IRunExecutionData = {
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
};
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(node.parameters);
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
workflow,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
try {
|
||||
await routingNode.runNode(
|
||||
inputData,
|
||||
runIndex,
|
||||
nodeTypeCopy,
|
||||
NodeExecuteFunctions,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
} catch (error) {
|
||||
// Do not fail any requests to allow custom error messages and
|
||||
// make logic easier
|
||||
if (error.cause.response) {
|
||||
const errorResponseData = {
|
||||
statusCode: error.cause.response.status,
|
||||
statusMessage: error.cause.response.statusText,
|
||||
};
|
||||
|
||||
if (credentialTestFunction.testRequest.rules) {
|
||||
// Special testing rules are defined so check all in order
|
||||
for (const rule of credentialTestFunction.testRequest.rules) {
|
||||
if (rule.type === 'responseCode') {
|
||||
if (errorResponseData.statusCode === rule.properties.value) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: rule.properties.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errorResponseData.statusCode < 199 || errorResponseData.statusCode > 299) {
|
||||
// All requests with response codes that are not 2xx are treated by default as failed
|
||||
return {
|
||||
status: 'Error',
|
||||
message:
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
errorResponseData.statusMessage ||
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Received HTTP status code: ${errorResponseData.statusCode}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'Error',
|
||||
message: error.message.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialsEncrypted,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
IDeferredPromise,
|
||||
IExecuteResponsePromiseData,
|
||||
|
@ -57,7 +56,10 @@ export interface ICustomRequest extends Request {
|
|||
}
|
||||
|
||||
export interface ICredentialsTypeData {
|
||||
[key: string]: ICredentialType;
|
||||
[key: string]: {
|
||||
className: string;
|
||||
sourcePath: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICredentialsOverwrite {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { CUSTOM_EXTENSION_ENV, UserSettings } from 'n8n-core';
|
|||
import {
|
||||
CodexData,
|
||||
ICredentialType,
|
||||
ICredentialTypeData,
|
||||
ILogger,
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
|
@ -35,9 +36,7 @@ const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
|
|||
class LoadNodesAndCredentialsClass {
|
||||
nodeTypes: INodeTypeData = {};
|
||||
|
||||
credentialTypes: {
|
||||
[key: string]: ICredentialType;
|
||||
} = {};
|
||||
credentialTypes: ICredentialTypeData = {};
|
||||
|
||||
excludeNodes: string[] | undefined = undefined;
|
||||
|
||||
|
@ -170,7 +169,10 @@ class LoadNodesAndCredentialsClass {
|
|||
}
|
||||
}
|
||||
|
||||
this.credentialTypes[tempCredential.name] = tempCredential;
|
||||
this.credentialTypes[tempCredential.name] = {
|
||||
type: tempCredential,
|
||||
sourcePath: filePath,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
/* eslint-disable no-await-in-loop */
|
||||
|
||||
import * as express from 'express';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { readFileSync } from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
||||
import { FindManyOptions, getConnectionManager, In, IsNull, LessThanOrEqual, Not } from 'typeorm';
|
||||
|
@ -52,37 +52,30 @@ import {
|
|||
BinaryDataManager,
|
||||
Credentials,
|
||||
IBinaryDataConfig,
|
||||
ICredentialTestFunctions,
|
||||
LoadNodeParameterOptions,
|
||||
NodeExecuteFunctions,
|
||||
UserSettings,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialsDecrypted,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
INodeCredentials,
|
||||
INodeCredentialsDetails,
|
||||
INodeCredentialTestRequest,
|
||||
INodeCredentialTestResult,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
INodeTypeNameVersion,
|
||||
INodeVersionedType,
|
||||
ITelemetrySettings,
|
||||
IWorkflowBase,
|
||||
LoggerProxy,
|
||||
NodeCredentialTestRequest,
|
||||
NodeCredentialTestResult,
|
||||
NodeHelpers,
|
||||
Workflow,
|
||||
ICredentialsEncrypted,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { NodeVersionedType } from 'n8n-nodes-base';
|
||||
|
||||
import * as basicAuth from 'basic-auth';
|
||||
import * as compression from 'compression';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
|
@ -1122,7 +1115,6 @@ class App {
|
|||
if (req.query.credentials !== undefined) {
|
||||
credentials = JSON.parse(req.query.credentials as string);
|
||||
}
|
||||
const methodName = req.query.methodName as string;
|
||||
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
|
@ -1137,7 +1129,20 @@ class App {
|
|||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(currentNodeParameters);
|
||||
|
||||
return loadDataInstance.getOptions(methodName, additionalData);
|
||||
if (req.query.methodName) {
|
||||
return loadDataInstance.getOptionsViaMethodName(
|
||||
req.query.methodName as string,
|
||||
additionalData,
|
||||
);
|
||||
}
|
||||
if (req.query.loadOptions) {
|
||||
return loadDataInstance.getOptionsViaRequestProperty(
|
||||
JSON.parse(req.query.loadOptions as string),
|
||||
additionalData,
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1433,87 +1438,25 @@ class App {
|
|||
this.app.post(
|
||||
`/${this.restEndpoint}/credentials-test`,
|
||||
ResponseHelper.send(
|
||||
async (req: express.Request, res: express.Response): Promise<NodeCredentialTestResult> => {
|
||||
const incomingData = req.body as NodeCredentialTestRequest;
|
||||
async (req: express.Request, res: express.Response): Promise<INodeCredentialTestResult> => {
|
||||
const incomingData = req.body as INodeCredentialTestRequest;
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
if (encryptionKey === undefined) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: 'No encryption key got found to decrypt the credentials!',
|
||||
};
|
||||
}
|
||||
|
||||
const credentialsHelper = new CredentialsHelper(encryptionKey);
|
||||
|
||||
const credentialType = incomingData.credentials.type;
|
||||
|
||||
// Find nodes that can test this credential.
|
||||
const nodeTypes = NodeTypes();
|
||||
const allNodes = nodeTypes.getAll();
|
||||
|
||||
let foundTestFunction:
|
||||
| ((
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
) => Promise<NodeCredentialTestResult>)
|
||||
| undefined;
|
||||
const nodeThatCanTestThisCredential = allNodes.find((node) => {
|
||||
if (
|
||||
incomingData.nodeToTestWith &&
|
||||
node.description.name !== incomingData.nodeToTestWith
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node instanceof NodeVersionedType) {
|
||||
const versionNames = Object.keys((node as INodeVersionedType).nodeVersions);
|
||||
for (const versionName of versionNames) {
|
||||
const nodeType = (node as INodeVersionedType).nodeVersions[
|
||||
versionName as unknown as number
|
||||
];
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
const credentialTestable = nodeType.description.credentials?.find((credential) => {
|
||||
const testFunctionSearch =
|
||||
credential.name === credentialType && !!credential.testedBy;
|
||||
if (testFunctionSearch) {
|
||||
foundTestFunction = (nodeType as unknown as INodeType).methods!.credentialTest![
|
||||
credential.testedBy!
|
||||
];
|
||||
}
|
||||
return testFunctionSearch;
|
||||
});
|
||||
if (credentialTestable) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const credentialTestable = (node as INodeType).description.credentials?.find(
|
||||
(credential) => {
|
||||
const testFunctionSearch =
|
||||
credential.name === credentialType && !!credential.testedBy;
|
||||
if (testFunctionSearch) {
|
||||
foundTestFunction = (node as INodeType).methods!.credentialTest![
|
||||
credential.testedBy!
|
||||
];
|
||||
}
|
||||
return testFunctionSearch;
|
||||
},
|
||||
);
|
||||
return !!credentialTestable;
|
||||
});
|
||||
|
||||
if (!nodeThatCanTestThisCredential) {
|
||||
return Promise.resolve({
|
||||
status: 'Error',
|
||||
message: 'There are no nodes that can test this credential.',
|
||||
});
|
||||
}
|
||||
|
||||
if (foundTestFunction === undefined) {
|
||||
return Promise.resolve({
|
||||
status: 'Error',
|
||||
message: 'No testing function found for this credential.',
|
||||
});
|
||||
}
|
||||
|
||||
const credentialTestFunctions = NodeExecuteFunctions.getCredentialTestFunctions();
|
||||
|
||||
const output = await foundTestFunction.call(
|
||||
credentialTestFunctions,
|
||||
return credentialsHelper.testCredentials(
|
||||
credentialType,
|
||||
incomingData.credentials,
|
||||
incomingData.nodeToTestWith,
|
||||
);
|
||||
return Promise.resolve(output);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -16,8 +16,6 @@ import {
|
|||
IRun,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
IWorkflowCredentials,
|
||||
LoggerProxy as Logger,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -32,8 +30,6 @@ import {
|
|||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
ResponseHelper,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
WorkflowCredentials,
|
||||
WorkflowRunner,
|
||||
} from '.';
|
||||
|
||||
|
@ -211,6 +207,32 @@ export function getAllNodeTypeData(): ITransferNodeTypes {
|
|||
return returnData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the defined CredentialTypes
|
||||
*
|
||||
* @export
|
||||
* @returns {ICredentialsTypeData}
|
||||
*/
|
||||
export function getAllCredentalsTypeData(): ICredentialsTypeData {
|
||||
const credentialTypes = CredentialTypes();
|
||||
|
||||
// Get the data of all the credential types that they
|
||||
// can be loaded again in the subprocess
|
||||
const returnData: ICredentialsTypeData = {};
|
||||
for (const credentialTypeName of Object.keys(credentialTypes.credentialTypes)) {
|
||||
if (credentialTypes.credentialTypes[credentialTypeName] === undefined) {
|
||||
throw new Error(`The CredentialType "${credentialTypeName}" could not be found!`);
|
||||
}
|
||||
|
||||
returnData[credentialTypeName] = {
|
||||
className: credentialTypes.credentialTypes[credentialTypeName].type.constructor.name,
|
||||
sourcePath: credentialTypes.credentialTypes[credentialTypeName].sourcePath,
|
||||
};
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of the node types that are needed
|
||||
* to execute the given nodes
|
||||
|
@ -256,7 +278,10 @@ export function getCredentialsDataWithParents(type: string): ICredentialsTypeDat
|
|||
const credentialType = credentialTypes.getByName(type);
|
||||
|
||||
const credentialTypeData: ICredentialsTypeData = {};
|
||||
credentialTypeData[type] = credentialType;
|
||||
credentialTypeData[type] = {
|
||||
className: credentialTypes.credentialTypes[type].type.constructor.name,
|
||||
sourcePath: credentialTypes.credentialTypes[type].sourcePath,
|
||||
};
|
||||
|
||||
if (credentialType === undefined || credentialType.extends === undefined) {
|
||||
return credentialTypeData;
|
||||
|
@ -267,7 +292,10 @@ export function getCredentialsDataWithParents(type: string): ICredentialsTypeDat
|
|||
continue;
|
||||
}
|
||||
|
||||
credentialTypeData[typeName] = credentialTypes.getByName(typeName);
|
||||
credentialTypeData[typeName] = {
|
||||
className: credentialTypes.credentialTypes[typeName].type.constructor.name,
|
||||
sourcePath: credentialTypes.credentialTypes[typeName].sourcePath,
|
||||
};
|
||||
Object.assign(credentialTypeData, getCredentialsDataWithParents(typeName));
|
||||
}
|
||||
|
||||
|
|
|
@ -592,7 +592,7 @@ export class WorkflowRunner {
|
|||
// be needed and so have to load all of them in the workflowRunnerProcess
|
||||
let loadAllNodeTypes = false;
|
||||
for (const node of data.workflowData.nodes) {
|
||||
if (node.type === 'n8n-nodes-base.executeWorkflow') {
|
||||
if (node.type === 'n8n-nodes-base.executeWorkflow' && node.disabled !== true) {
|
||||
loadAllNodeTypes = true;
|
||||
break;
|
||||
}
|
||||
|
@ -604,8 +604,7 @@ export class WorkflowRunner {
|
|||
if (loadAllNodeTypes) {
|
||||
// Supply all nodeTypes and credentialTypes
|
||||
nodeTypeData = WorkflowHelpers.getAllNodeTypeData();
|
||||
const credentialTypes = CredentialTypes();
|
||||
credentialTypeData = credentialTypes.credentialTypes;
|
||||
credentialTypeData = WorkflowHelpers.getAllCredentalsTypeData();
|
||||
} else {
|
||||
// Supply only nodeTypes, credentialTypes and overwrites that the workflow needs
|
||||
nodeTypeData = WorkflowHelpers.getNodeTypeData(data.workflowData.nodes);
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
|
||||
import {
|
||||
ExecutionError,
|
||||
ICredentialType,
|
||||
ICredentialTypeData,
|
||||
IDataObject,
|
||||
IExecuteResponsePromiseData,
|
||||
IExecuteWorkflowInfo,
|
||||
|
@ -94,10 +96,12 @@ export class WorkflowRunnerProcess {
|
|||
|
||||
let className: string;
|
||||
let tempNode: INodeType;
|
||||
let tempCredential: ICredentialType;
|
||||
let filePath: string;
|
||||
|
||||
this.startedAt = new Date();
|
||||
|
||||
// Load the required nodes
|
||||
const nodeTypesData: INodeTypeData = {};
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const nodeTypeName of Object.keys(this.data.nodeTypeData)) {
|
||||
|
@ -131,9 +135,32 @@ export class WorkflowRunnerProcess {
|
|||
const nodeTypes = NodeTypes();
|
||||
await nodeTypes.init(nodeTypesData);
|
||||
|
||||
// Load the required credentials
|
||||
const credentialsTypeData: ICredentialTypeData = {};
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const credentialTypeName of Object.keys(this.data.credentialsTypeData)) {
|
||||
className = this.data.credentialsTypeData[credentialTypeName].className;
|
||||
|
||||
filePath = this.data.credentialsTypeData[credentialTypeName].sourcePath;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
|
||||
const tempModule = require(filePath);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
tempCredential = new tempModule[className]() as ICredentialType;
|
||||
} catch (error) {
|
||||
throw new Error(`Error loading credential "${credentialTypeName}" from: "${filePath}"`);
|
||||
}
|
||||
|
||||
credentialsTypeData[credentialTypeName] = {
|
||||
type: tempCredential,
|
||||
sourcePath: filePath,
|
||||
};
|
||||
}
|
||||
|
||||
// Init credential types the workflow uses (is needed to apply default values to credentials)
|
||||
const credentialTypes = CredentialTypes();
|
||||
await credentialTypes.init(inputData.credentialsTypeData);
|
||||
await credentialTypes.init(credentialsTypeData);
|
||||
|
||||
// Load the credentials overwrites if any exist
|
||||
const credentialsOverwrites = CredentialsOverwrites();
|
||||
|
|
305
packages/cli/test/CredentialsHelper.test.ts
Normal file
305
packages/cli/test/CredentialsHelper.test.ts
Normal file
|
@ -0,0 +1,305 @@
|
|||
import { CredentialsHelper, CredentialTypes } from '../src';
|
||||
import * as Helpers from './Helpers';
|
||||
import {
|
||||
IAuthenticateBasicAuth,
|
||||
IAuthenticateBearer,
|
||||
IAuthenticateHeaderAuth,
|
||||
IAuthenticateQueryAuth,
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialType,
|
||||
ICredentialTypeData,
|
||||
IHttpRequestOptions,
|
||||
INode,
|
||||
INodeProperties,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const TEST_ENCRYPTION_KEY = 'test';
|
||||
|
||||
describe('CredentialsHelper', () => {
|
||||
describe('authenticate', () => {
|
||||
const tests: Array<{
|
||||
description: string;
|
||||
input: {
|
||||
credentials: ICredentialDataDecryptedObject;
|
||||
credentialType: ICredentialType;
|
||||
};
|
||||
output: IHttpRequestOptions;
|
||||
}> = [
|
||||
{
|
||||
description: 'built-in basicAuth, default property names',
|
||||
input: {
|
||||
credentials: {
|
||||
user: 'user1',
|
||||
password: 'password1',
|
||||
},
|
||||
credentialType: new (class TestApi implements ICredentialType {
|
||||
name = 'testApi';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'User',
|
||||
name: 'user',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'basicAuth',
|
||||
properties: {},
|
||||
} as IAuthenticateBasicAuth;
|
||||
})(),
|
||||
},
|
||||
output: {
|
||||
url: '',
|
||||
headers: {},
|
||||
auth: { username: 'user1', password: 'password1' },
|
||||
qs: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'built-in basicAuth, custom property names',
|
||||
input: {
|
||||
credentials: {
|
||||
customUser: 'user2',
|
||||
customPassword: 'password2',
|
||||
},
|
||||
credentialType: new (class TestApi implements ICredentialType {
|
||||
name = 'testApi';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'User',
|
||||
name: 'user',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'basicAuth',
|
||||
properties: {
|
||||
userPropertyName: 'customUser',
|
||||
passwordPropertyName: 'customPassword',
|
||||
},
|
||||
} as IAuthenticateBasicAuth;
|
||||
})(),
|
||||
},
|
||||
output: {
|
||||
url: '',
|
||||
headers: {},
|
||||
auth: { username: 'user2', password: 'password2' },
|
||||
qs: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'built-in headerAuth',
|
||||
input: {
|
||||
credentials: {
|
||||
accessToken: 'test',
|
||||
},
|
||||
credentialType: new (class TestApi implements ICredentialType {
|
||||
name = 'testApi';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'headerAuth',
|
||||
properties: {
|
||||
name: 'Authorization',
|
||||
value: '=Bearer {{$credentials.accessToken}}',
|
||||
},
|
||||
} as IAuthenticateHeaderAuth;
|
||||
})(),
|
||||
},
|
||||
output: { url: '', headers: { Authorization: 'Bearer test' }, qs: {} },
|
||||
},
|
||||
{
|
||||
description: 'built-in bearer, default property name',
|
||||
input: {
|
||||
credentials: {
|
||||
accessToken: 'test',
|
||||
},
|
||||
credentialType: new (class TestApi implements ICredentialType {
|
||||
name = 'testApi';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'bearer',
|
||||
properties: {},
|
||||
} as IAuthenticateBearer;
|
||||
})(),
|
||||
},
|
||||
output: { url: '', headers: { Authorization: 'Bearer test' }, qs: {} },
|
||||
},
|
||||
{
|
||||
description: 'built-in bearer, custom property name',
|
||||
input: {
|
||||
credentials: {
|
||||
myToken: 'test',
|
||||
},
|
||||
credentialType: new (class TestApi implements ICredentialType {
|
||||
name = 'testApi';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'My Token',
|
||||
name: 'myToken',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'bearer',
|
||||
properties: {
|
||||
tokenPropertyName: 'myToken',
|
||||
},
|
||||
} as IAuthenticateBearer;
|
||||
})(),
|
||||
},
|
||||
output: { url: '', headers: { Authorization: 'Bearer test' }, qs: {} },
|
||||
},
|
||||
{
|
||||
description: 'built-in queryAuth',
|
||||
input: {
|
||||
credentials: {
|
||||
accessToken: 'test',
|
||||
},
|
||||
credentialType: new (class TestApi implements ICredentialType {
|
||||
name = 'testApi';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'queryAuth',
|
||||
properties: {
|
||||
key: 'accessToken',
|
||||
value: '={{$credentials.accessToken}}',
|
||||
},
|
||||
} as IAuthenticateQueryAuth;
|
||||
})(),
|
||||
},
|
||||
output: { url: '', headers: {}, qs: { accessToken: 'test' } },
|
||||
},
|
||||
{
|
||||
description: 'custom authentication',
|
||||
input: {
|
||||
credentials: {
|
||||
accessToken: 'test',
|
||||
user: 'testUser',
|
||||
},
|
||||
credentialType: new (class TestApi implements ICredentialType {
|
||||
name = 'testApi';
|
||||
displayName = 'Test API';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'My Token',
|
||||
name: 'myToken',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
async authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
requestOptions.headers!['Authorization'] = `Bearer ${credentials.accessToken}`;
|
||||
requestOptions.qs!['user'] = credentials.user;
|
||||
return requestOptions;
|
||||
}
|
||||
})(),
|
||||
},
|
||||
output: {
|
||||
url: '',
|
||||
headers: { Authorization: 'Bearer test' },
|
||||
qs: { user: 'testUser' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const node: INode = {
|
||||
parameters: {},
|
||||
name: 'test',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
};
|
||||
|
||||
const incomingRequestOptions = {
|
||||
url: '',
|
||||
headers: {},
|
||||
qs: {},
|
||||
};
|
||||
|
||||
const nodeTypes = Helpers.NodeTypes();
|
||||
|
||||
const workflow = new Workflow({
|
||||
nodes: [node],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
for (const testData of tests) {
|
||||
test(testData.description, async () => {
|
||||
const credentialTypes: ICredentialTypeData = {
|
||||
[testData.input.credentialType.name]: {
|
||||
type: testData.input.credentialType,
|
||||
sourcePath: '',
|
||||
},
|
||||
};
|
||||
|
||||
await CredentialTypes().init(credentialTypes);
|
||||
|
||||
const credentialsHelper = new CredentialsHelper(TEST_ENCRYPTION_KEY);
|
||||
|
||||
const result = await credentialsHelper.authenticate(
|
||||
testData.input.credentials,
|
||||
testData.input.credentialType.name,
|
||||
JSON.parse(JSON.stringify(incomingRequestOptions)),
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
|
||||
expect(result).toEqual(testData.output);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
63
packages/cli/test/Helpers.ts
Normal file
63
packages/cli/test/Helpers.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { INodeType, INodeTypeData, INodeTypes, NodeHelpers } from 'n8n-workflow';
|
||||
|
||||
class NodeTypesClass implements INodeTypes {
|
||||
nodeTypes: INodeTypeData = {
|
||||
'test.set': {
|
||||
sourcePath: '',
|
||||
type: {
|
||||
description: {
|
||||
displayName: 'Set',
|
||||
name: 'set',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
description: 'Sets a value',
|
||||
defaults: {
|
||||
name: 'Set',
|
||||
color: '#0000FF',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Value1',
|
||||
name: 'value1',
|
||||
type: 'string',
|
||||
default: 'default-value1',
|
||||
},
|
||||
{
|
||||
displayName: 'Value2',
|
||||
name: 'value2',
|
||||
type: 'string',
|
||||
default: 'default-value2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
||||
|
||||
getAll(): INodeType[] {
|
||||
console.log('1234');
|
||||
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedNodeType(data.type));
|
||||
}
|
||||
|
||||
getByName(nodeType: string): INodeType {
|
||||
return this.getByNameAndVersion(nodeType);
|
||||
}
|
||||
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
||||
}
|
||||
}
|
||||
|
||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||
|
||||
export function NodeTypes(): NodeTypesClass {
|
||||
if (nodeTypesInstance === undefined) {
|
||||
nodeTypesInstance = new NodeTypesClass();
|
||||
}
|
||||
|
||||
return nodeTypesInstance;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
describe('Placeholder', () => {
|
||||
test('example', () => {
|
||||
expect(1 + 1).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
IAdditionalCredentialOptions,
|
||||
IAllExecuteFunctions,
|
||||
IBinaryData,
|
||||
ICredentialTestFunctions as ICredentialTestFunctionsBase,
|
||||
|
@ -43,6 +44,12 @@ export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
|||
): Promise<IBinaryData>;
|
||||
getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>;
|
||||
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -55,6 +62,11 @@ export interface IExecuteFunctions extends IExecuteFunctionsBase {
|
|||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -67,6 +79,12 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
|||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -78,6 +96,11 @@ export interface IExecuteSingleFunctions extends IExecuteSingleFunctionsBase {
|
|||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -90,6 +113,12 @@ export interface IPollFunctions extends IPollFunctionsBase {
|
|||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -102,6 +131,11 @@ export interface IPollFunctions extends IPollFunctionsBase {
|
|||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -118,6 +152,12 @@ export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
|||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -130,6 +170,11 @@ export interface ITriggerFunctions extends ITriggerFunctionsBase {
|
|||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -152,6 +197,12 @@ export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
|||
helpers: {
|
||||
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||
request?: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
requestOAuth2?: (
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -163,6 +214,11 @@ export interface ILoadOptionsFunctions extends ILoadOptionsFunctionsBase {
|
|||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -176,6 +232,12 @@ export interface IHookFunctions extends IHookFunctionsBase {
|
|||
helpers: {
|
||||
httpRequest(requestOptions: IHttpRequestOptions): Promise<any>; // tslint:disable-line:no-any
|
||||
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -187,6 +249,11 @@ export interface IHookFunctions extends IHookFunctionsBase {
|
|||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -199,6 +266,12 @@ export interface IWebhookFunctions extends IWebhookFunctionsBase {
|
|||
mimeType?: string,
|
||||
): Promise<IBinaryData>;
|
||||
request: (uriOrObject: string | IDataObject | any, options?: IDataObject) => Promise<any>; // tslint:disable-line:no-any
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -211,6 +284,11 @@ export interface IWebhookFunctions extends IWebhookFunctionsBase {
|
|||
requestOptions: OptionsWithUrl | requestPromise.RequestPromiseOptions,
|
||||
): Promise<any>; // tslint:disable-line:no-any
|
||||
returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExecutionData[];
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import {
|
||||
ILoadOptions,
|
||||
INode,
|
||||
INodeCredentials,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeNameVersion,
|
||||
INodeTypes,
|
||||
IRunExecutionData,
|
||||
ITaskDataConnections,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
RoutingNode,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -22,6 +31,8 @@ const TEMP_NODE_NAME = 'Temp-Node';
|
|||
const TEMP_WORKFLOW_NAME = 'Temp-Workflow';
|
||||
|
||||
export class LoadNodeParameterOptions {
|
||||
currentNodeParameters: INodeParameters;
|
||||
|
||||
path: string;
|
||||
|
||||
workflow: Workflow;
|
||||
|
@ -37,6 +48,7 @@ export class LoadNodeParameterOptions {
|
|||
nodeTypeNameAndVersion.name,
|
||||
nodeTypeNameAndVersion.version,
|
||||
);
|
||||
this.currentNodeParameters = currentNodeParameters;
|
||||
this.path = path;
|
||||
if (nodeType === undefined) {
|
||||
throw new Error(
|
||||
|
@ -87,14 +99,14 @@ export class LoadNodeParameterOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the available options
|
||||
* Returns the available options via a predefined method
|
||||
*
|
||||
* @param {string} methodName The name of the method of which to get the data from
|
||||
* @param {IWorkflowExecuteAdditionalData} additionalData
|
||||
* @returns {Promise<INodePropertyOptions[]>}
|
||||
* @memberof LoadNodeParameterOptions
|
||||
*/
|
||||
async getOptions(
|
||||
async getOptionsViaMethodName(
|
||||
methodName: string,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
|
@ -122,4 +134,93 @@ export class LoadNodeParameterOptions {
|
|||
|
||||
return nodeType.methods.loadOptions[methodName].call(thisArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available options via a load request informatoin
|
||||
*
|
||||
* @param {ILoadOptions} loadOptions The load options which also contain the request information
|
||||
* @param {IWorkflowExecuteAdditionalData} additionalData
|
||||
* @returns {Promise<INodePropertyOptions[]>}
|
||||
* @memberof LoadNodeParameterOptions
|
||||
*/
|
||||
async getOptionsViaRequestProperty(
|
||||
loadOptions: ILoadOptions,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const node = this.workflow.getNode(TEMP_NODE_NAME);
|
||||
|
||||
const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node!.type, node?.typeVersion);
|
||||
|
||||
if (
|
||||
nodeType === undefined ||
|
||||
!nodeType.description.requestDefaults ||
|
||||
!nodeType.description.requestDefaults.baseURL
|
||||
) {
|
||||
// This in in here for now for security reasons.
|
||||
// Background: As the full data for the request to make does get send, and the auth data
|
||||
// will then be applied, would it be possible to retrieve that data like that. By at least
|
||||
// requiring a baseURL to be defined can at least not a random server be called.
|
||||
// In the future this code has to get improved that it does not use the request information from
|
||||
// the request rather resolves it via the parameter-path and nodeType data.
|
||||
throw new Error(
|
||||
`The node-type "${
|
||||
node!.type
|
||||
}" does not exist or does not have "requestDefaults.baseURL" defined!`,
|
||||
);
|
||||
}
|
||||
|
||||
const mode = 'internal';
|
||||
const runIndex = 0;
|
||||
const connectionInputData: INodeExecutionData[] = [];
|
||||
const runExecutionData: IRunExecutionData = { resultData: { runData: {} } };
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
this.workflow,
|
||||
node!,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
// Create copy of node-type with the single property we want to get the data off
|
||||
const tempNode: INodeType = {
|
||||
...nodeType,
|
||||
...{
|
||||
description: {
|
||||
...nodeType.description,
|
||||
properties: [
|
||||
{
|
||||
displayName: '',
|
||||
type: 'string',
|
||||
name: '',
|
||||
default: '',
|
||||
routing: loadOptions.routing,
|
||||
} as INodeProperties,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const inputData: ITaskDataConnections = {
|
||||
main: [[{ json: {} }]],
|
||||
};
|
||||
|
||||
const optionsData = await routingNode.runNode(
|
||||
inputData,
|
||||
runIndex,
|
||||
tempNode,
|
||||
NodeExecuteFunctions,
|
||||
);
|
||||
|
||||
if (optionsData?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(optionsData)) {
|
||||
throw new Error('The returned data is not an array!');
|
||||
}
|
||||
|
||||
return optionsData[0].map((item) => item.json) as unknown as INodePropertyOptions[];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
import {
|
||||
GenericValue,
|
||||
IAdditionalCredentialOptions,
|
||||
IAllExecuteFunctions,
|
||||
IBinaryData,
|
||||
IContextObject,
|
||||
|
@ -29,6 +30,8 @@ import {
|
|||
IN8nHttpFullResponse,
|
||||
IN8nHttpResponse,
|
||||
INode,
|
||||
INodeCredentialDescription,
|
||||
INodeCredentialsDetails,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeType,
|
||||
|
@ -44,6 +47,7 @@ import {
|
|||
IWorkflowDataProxyData,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
IWorkflowMetadata,
|
||||
NodeApiError,
|
||||
NodeHelpers,
|
||||
NodeOperationError,
|
||||
NodeParameterValue,
|
||||
|
@ -676,6 +680,10 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest
|
|||
|
||||
axiosRequest.params = n8nRequest.qs;
|
||||
|
||||
if (n8nRequest.baseURL !== undefined) {
|
||||
axiosRequest.baseURL = n8nRequest.baseURL;
|
||||
}
|
||||
|
||||
if (n8nRequest.disableFollowRedirect === true) {
|
||||
axiosRequest.maxRedirects = 0;
|
||||
}
|
||||
|
@ -733,12 +741,11 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest
|
|||
}
|
||||
|
||||
async function httpRequest(
|
||||
requestParams: IHttpRequestOptions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
|
||||
// tslint:disable-line:no-any
|
||||
const axiosRequest = convertN8nRequestToAxios(requestParams);
|
||||
const axiosRequest = convertN8nRequestToAxios(requestOptions);
|
||||
const result = await axios(axiosRequest);
|
||||
if (requestParams.returnFullResponse) {
|
||||
if (requestOptions.returnFullResponse) {
|
||||
return {
|
||||
body: result.data,
|
||||
headers: result.headers,
|
||||
|
@ -853,10 +860,11 @@ export async function prepareBinaryData(
|
|||
export async function requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions | IHttpRequestOptions,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
oAuth2Options?: IOAuth2Options,
|
||||
isN8nRequest = false,
|
||||
) {
|
||||
const credentials = (await this.getCredentials(
|
||||
credentialsType,
|
||||
|
@ -952,7 +960,9 @@ export async function requestOAuth2(
|
|||
|
||||
// Make the request again with the new token
|
||||
const newRequestOptions = newToken.sign(requestOptions as clientOAuth2.RequestObject);
|
||||
|
||||
if (isN8nRequest) {
|
||||
return this.helpers.httpRequest(newRequestOptions);
|
||||
}
|
||||
return this.helpers.request!(newRequestOptions);
|
||||
}
|
||||
|
||||
|
@ -972,7 +982,12 @@ export async function requestOAuth2(
|
|||
export async function requestOAuth1(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUrl | OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
requestOptions:
|
||||
| OptionsWithUrl
|
||||
| OptionsWithUri
|
||||
| requestPromise.RequestPromiseOptions
|
||||
| IHttpRequestOptions,
|
||||
isN8nRequest = false,
|
||||
) {
|
||||
const credentials = (await this.getCredentials(
|
||||
credentialsType,
|
||||
|
@ -1020,12 +1035,71 @@ export async function requestOAuth1(
|
|||
// @ts-ignore
|
||||
requestOptions.headers = oauth.toHeader(oauth.authorize(requestOptions, token));
|
||||
|
||||
if (isN8nRequest) {
|
||||
return this.helpers.httpRequest(requestOptions as IHttpRequestOptions);
|
||||
}
|
||||
|
||||
return this.helpers.request!(requestOptions).catch(async (error: IResponseError) => {
|
||||
// Unknown error so simply throw it
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
export async function httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
) {
|
||||
try {
|
||||
const parentTypes = additionalData.credentialsHelper.getParentTypes(credentialsType);
|
||||
|
||||
if (parentTypes.includes('oAuth1Api')) {
|
||||
return await requestOAuth1.call(this, credentialsType, requestOptions, true);
|
||||
}
|
||||
if (parentTypes.includes('oAuth2Api')) {
|
||||
return await requestOAuth2.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions?.oauth2,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
let credentialsDecrypted: ICredentialDataDecryptedObject | undefined;
|
||||
if (additionalCredentialOptions?.credentialsDecrypted) {
|
||||
credentialsDecrypted = additionalCredentialOptions.credentialsDecrypted.data;
|
||||
} else {
|
||||
credentialsDecrypted = await this.getCredentials(credentialsType);
|
||||
}
|
||||
|
||||
if (credentialsDecrypted === undefined) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
|
||||
);
|
||||
}
|
||||
|
||||
requestOptions = await additionalData.credentialsHelper.authenticate(
|
||||
credentialsDecrypted,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
|
||||
return await httpRequest(requestOptions);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes generic input data and brings it into the json format n8n uses.
|
||||
*
|
||||
|
@ -1047,6 +1121,62 @@ export function returnJsonArray(jsonData: IDataObject | IDataObject[]): INodeExe
|
|||
return returnData;
|
||||
}
|
||||
|
||||
// TODO: Move up later
|
||||
export async function requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
) {
|
||||
try {
|
||||
const parentTypes = additionalData.credentialsHelper.getParentTypes(credentialsType);
|
||||
|
||||
if (parentTypes.includes('oAuth1Api')) {
|
||||
return await requestOAuth1.call(this, credentialsType, requestOptions, false);
|
||||
}
|
||||
if (parentTypes.includes('oAuth2Api')) {
|
||||
return await requestOAuth2.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions?.oauth2,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
let credentialsDecrypted: ICredentialDataDecryptedObject | undefined;
|
||||
if (additionalCredentialOptions?.credentialsDecrypted) {
|
||||
credentialsDecrypted = additionalCredentialOptions.credentialsDecrypted.data;
|
||||
} else {
|
||||
credentialsDecrypted = await this.getCredentials(credentialsType);
|
||||
}
|
||||
|
||||
if (credentialsDecrypted === undefined) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
|
||||
);
|
||||
}
|
||||
|
||||
requestOptions = await additionalData.credentialsHelper.authenticate(
|
||||
credentialsDecrypted,
|
||||
credentialsType,
|
||||
requestOptions as IHttpRequestOptions,
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
|
||||
return await proxyRequestToAxios(requestOptions as IDataObject);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional keys for Expressions and Function-Nodes
|
||||
*
|
||||
|
@ -1094,39 +1224,46 @@ export async function getCredentials(
|
|||
);
|
||||
}
|
||||
|
||||
if (nodeType.description.credentials === undefined) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Node type "${node.type}" does not have any credentials defined!`,
|
||||
);
|
||||
}
|
||||
// Hardcode for now for security reasons that only a single node can access
|
||||
// all credentials
|
||||
const fullAccess = ['n8n-nodes-base.httpRequest'].includes(node.type);
|
||||
|
||||
const nodeCredentialDescription = nodeType.description.credentials.find(
|
||||
(credentialTypeDescription) => credentialTypeDescription.name === type,
|
||||
);
|
||||
if (nodeCredentialDescription === undefined) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Node type "${node.type}" does not have any credentials of type "${type}" defined!`,
|
||||
);
|
||||
}
|
||||
let nodeCredentialDescription: INodeCredentialDescription | undefined;
|
||||
if (!fullAccess) {
|
||||
if (nodeType.description.credentials === undefined) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Node type "${node.type}" does not have any credentials defined!`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!NodeHelpers.displayParameter(
|
||||
additionalData.currentNodeParameters || node.parameters,
|
||||
nodeCredentialDescription,
|
||||
node.parameters,
|
||||
)
|
||||
) {
|
||||
// Credentials should not be displayed so return undefined even if they would be defined
|
||||
return undefined;
|
||||
nodeCredentialDescription = nodeType.description.credentials.find(
|
||||
(credentialTypeDescription) => credentialTypeDescription.name === type,
|
||||
);
|
||||
if (nodeCredentialDescription === undefined) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Node type "${node.type}" does not have any credentials of type "${type}" defined!`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!NodeHelpers.displayParameter(
|
||||
additionalData.currentNodeParameters || node.parameters,
|
||||
nodeCredentialDescription,
|
||||
node.parameters,
|
||||
)
|
||||
) {
|
||||
// Credentials should not be displayed so return undefined even if they would be defined
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if node has any credentials defined
|
||||
if (!node.credentials || !node.credentials[type]) {
|
||||
if (!fullAccess && (!node.credentials || !node.credentials[type])) {
|
||||
// If none are defined check if the credentials are required or not
|
||||
|
||||
if (nodeCredentialDescription.required === true) {
|
||||
if (nodeCredentialDescription?.required === true) {
|
||||
// Credentials are required so error
|
||||
if (!node.credentials) {
|
||||
throw new NodeOperationError(node, 'Node does not have any credentials set!');
|
||||
|
@ -1140,6 +1277,12 @@ export async function getCredentials(
|
|||
}
|
||||
}
|
||||
|
||||
if (fullAccess && (!node.credentials || !node.credentials[type])) {
|
||||
// Make sure that fullAccess nodes still behave like before that if they
|
||||
// request access to credentials that are currently not set it returns undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let expressionResolveValues: ICredentialsExpressionResolveValues | undefined;
|
||||
if (connectionInputData && runExecutionData && runIndex !== undefined) {
|
||||
expressionResolveValues = {
|
||||
|
@ -1152,7 +1295,9 @@ export async function getCredentials(
|
|||
} as ICredentialsExpressionResolveValues;
|
||||
}
|
||||
|
||||
const nodeCredentials = node.credentials[type];
|
||||
const nodeCredentials = node.credentials
|
||||
? node.credentials[type]
|
||||
: ({} as INodeCredentialsDetails);
|
||||
|
||||
// TODO: solve using credentials via expression
|
||||
// if (name.charAt(0) === '=') {
|
||||
|
@ -1466,6 +1611,22 @@ export function getExecutePollFunctions(
|
|||
);
|
||||
},
|
||||
request: proxyRequestToAxios,
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
async requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
|
@ -1488,6 +1649,22 @@ export function getExecutePollFunctions(
|
|||
): Promise<any> {
|
||||
return requestOAuth1.call(this, credentialsType, requestOptions);
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return httpRequestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
returnJsonArray,
|
||||
},
|
||||
};
|
||||
|
@ -1570,6 +1747,22 @@ export function getExecuteTriggerFunctions(
|
|||
},
|
||||
helpers: {
|
||||
httpRequest,
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
async prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
|
@ -1606,6 +1799,22 @@ export function getExecuteTriggerFunctions(
|
|||
): Promise<any> {
|
||||
return requestOAuth1.call(this, credentialsType, requestOptions);
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return httpRequestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
returnJsonArray,
|
||||
},
|
||||
};
|
||||
|
@ -1784,6 +1993,22 @@ export function getExecuteFunctions(
|
|||
},
|
||||
helpers: {
|
||||
httpRequest,
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
async prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
|
@ -1827,6 +2052,22 @@ export function getExecuteFunctions(
|
|||
): Promise<any> {
|
||||
return requestOAuth1.call(this, credentialsType, requestOptions);
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return httpRequestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
returnJsonArray,
|
||||
},
|
||||
};
|
||||
|
@ -1977,6 +2218,22 @@ export function getExecuteSingleFunctions(
|
|||
},
|
||||
helpers: {
|
||||
httpRequest,
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
async prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
|
@ -2013,6 +2270,22 @@ export function getExecuteSingleFunctions(
|
|||
): Promise<any> {
|
||||
return requestOAuth1.call(this, credentialsType, requestOptions);
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return httpRequestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
})(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex);
|
||||
|
@ -2104,6 +2377,22 @@ export function getLoadOptionsFunctions(
|
|||
},
|
||||
helpers: {
|
||||
httpRequest,
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
request: proxyRequestToAxios,
|
||||
async requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
|
@ -2127,6 +2416,22 @@ export function getLoadOptionsFunctions(
|
|||
): Promise<any> {
|
||||
return requestOAuth1.call(this, credentialsType, requestOptions);
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return httpRequestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
return that;
|
||||
|
@ -2224,6 +2529,22 @@ export function getExecuteHookFunctions(
|
|||
},
|
||||
helpers: {
|
||||
httpRequest,
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
request: proxyRequestToAxios,
|
||||
async requestOAuth2(
|
||||
this: IAllExecuteFunctions,
|
||||
|
@ -2247,6 +2568,22 @@ export function getExecuteHookFunctions(
|
|||
): Promise<any> {
|
||||
return requestOAuth1.call(this, credentialsType, requestOptions);
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return httpRequestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
return that;
|
||||
|
@ -2370,6 +2707,22 @@ export function getExecuteWebhookFunctions(
|
|||
prepareOutputData: NodeHelpers.prepareOutputData,
|
||||
helpers: {
|
||||
httpRequest,
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
async prepareBinaryData(
|
||||
binaryData: Buffer,
|
||||
filePath?: string,
|
||||
|
@ -2406,6 +2759,22 @@ export function getExecuteWebhookFunctions(
|
|||
): Promise<any> {
|
||||
return requestOAuth1.call(this, credentialsType, requestOptions);
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return httpRequestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
additionalCredentialOptions,
|
||||
);
|
||||
},
|
||||
returnJsonArray,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
IDataObject,
|
||||
IDeferredPromise,
|
||||
IExecuteWorkflowInfo,
|
||||
IHttpRequestOptions,
|
||||
INodeCredentialsDetails,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
|
@ -24,17 +25,30 @@ import {
|
|||
import { Credentials, IExecuteFunctions } from '../src';
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
getDecrypted(
|
||||
async authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
requestParams: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
getParentTypes(name: string): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getDecrypted(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
): Promise<ICredentialDataDecryptedObject> {
|
||||
return new Promise((res) => res({}));
|
||||
return {};
|
||||
}
|
||||
|
||||
getCredentials(nodeCredentials: INodeCredentialsDetails, type: string): Promise<Credentials> {
|
||||
return new Promise((res) => {
|
||||
res(new Credentials({ id: null, name: '' }, '', [], ''));
|
||||
});
|
||||
async getCredentials(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
): Promise<Credentials> {
|
||||
return new Credentials({ id: null, name: '' }, '', [], '');
|
||||
}
|
||||
|
||||
async updateCredentials(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2017"
|
||||
"es2019"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
|
@ -16,7 +16,7 @@
|
|||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist/",
|
||||
"target": "es2017",
|
||||
"target": "es2019",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
|
||||
import {
|
||||
GenericValue,
|
||||
IConnections,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialsEncrypted,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
GenericValue,
|
||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||
ILoadOptions,
|
||||
INode,
|
||||
INodeCredentials,
|
||||
INodeIssues,
|
||||
|
@ -19,6 +19,7 @@ import {
|
|||
IRunData,
|
||||
ITaskData,
|
||||
ITelemetrySettings,
|
||||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -167,7 +168,7 @@ export interface IRestApi {
|
|||
getNodeTranslationHeaders(): Promise<INodeTranslationHeaders>;
|
||||
getNodeTypes(onlyLatest?: boolean): Promise<INodeTypeDescription[]>;
|
||||
getNodesInformation(nodeInfos: INodeTypeNameVersion[]): Promise<INodeTypeDescription[]>;
|
||||
getNodeParameterOptions(nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]>;
|
||||
getNodeParameterOptions(sendData: { nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName?: string, loadOptions?: ILoadOptions, currentNodeParameters: INodeParameters, credentials?: INodeCredentials }): Promise<INodePropertyOptions[]> ;
|
||||
removeTestWebhook(workflowId: string): Promise<boolean>;
|
||||
runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>;
|
||||
createNewWorkflow(sendData: IWorkflowDataUpdate): Promise<IWorkflowDb>;
|
||||
|
|
|
@ -4,8 +4,8 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
NodeCredentialTestRequest,
|
||||
NodeCredentialTestResult,
|
||||
INodeCredentialTestRequest,
|
||||
INodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function getCredentialTypes(context: IRestApiContext): Promise<ICredentialType[]> {
|
||||
|
@ -48,6 +48,6 @@ export async function oAuth2CredentialAuthorize(context: IRestApiContext, data:
|
|||
return makeRestApiRequest(context, 'GET', `/oauth2-credential/auth`, data as unknown as IDataObject);
|
||||
}
|
||||
|
||||
export async function testCredential(context: IRestApiContext, data: NodeCredentialTestRequest): Promise<NodeCredentialTestResult> {
|
||||
export async function testCredential(context: IRestApiContext, data: INodeCredentialTestRequest): Promise<INodeCredentialTestResult> {
|
||||
return makeRestApiRequest(context, 'POST', '/credentials-test', data as unknown as IDataObject);
|
||||
}
|
||||
|
|
|
@ -108,10 +108,10 @@ import {
|
|||
ICredentialNodeAccess,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialType,
|
||||
INodeCredentialTestResult,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeHelpers,
|
||||
} from 'n8n-workflow';
|
||||
import CredentialIcon from '../CredentialIcon.vue';
|
||||
|
@ -279,7 +279,7 @@ export default mixins(showMessage, nodeHelpers).extend({
|
|||
return false;
|
||||
});
|
||||
|
||||
return !!nodesThatCanTest.length;
|
||||
return !!nodesThatCanTest.length || (!!this.credentialType && !!this.credentialType.test);
|
||||
},
|
||||
nodesWithAccess(): INodeTypeDescription[] {
|
||||
if (this.credentialTypeName) {
|
||||
|
@ -566,7 +566,7 @@ export default mixins(showMessage, nodeHelpers).extend({
|
|||
},
|
||||
|
||||
async testCredential(credentialDetails: ICredentialsDecrypted) {
|
||||
const result: NodeCredentialTestResult = await this.$store.dispatch('credentials/testCredential', credentialDetails);
|
||||
const result: INodeCredentialTestResult = await this.$store.dispatch('credentials/testCredential', credentialDetails);
|
||||
if (result.status === 'Error') {
|
||||
this.authError = result.message;
|
||||
this.testedSuccessfully = false;
|
||||
|
|
|
@ -200,6 +200,8 @@ import {
|
|||
import {
|
||||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
IHttpRequestOptions,
|
||||
ILoadOptions,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
Workflow,
|
||||
|
@ -517,7 +519,7 @@ export default mixins(
|
|||
return this.getArgument('editor') as string;
|
||||
},
|
||||
parameterOptions (): INodePropertyOptions[] {
|
||||
if (this.remoteMethod === undefined) {
|
||||
if (this.hasRemoteMethod === false) {
|
||||
// Options are already given
|
||||
return this.parameter.options;
|
||||
}
|
||||
|
@ -560,8 +562,8 @@ export default mixins(
|
|||
|
||||
return styles;
|
||||
},
|
||||
remoteMethod (): string | undefined {
|
||||
return this.getArgument('loadOptionsMethod') as string | undefined;
|
||||
hasRemoteMethod (): boolean {
|
||||
return !!this.getArgument('loadOptionsMethod') || !!this.getArgument('loadOptions');
|
||||
},
|
||||
shortPath (): string {
|
||||
const shortPath = this.path.split('.');
|
||||
|
@ -590,7 +592,7 @@ export default mixins(
|
|||
},
|
||||
|
||||
async loadRemoteParameterOptions () {
|
||||
if (this.node === null || this.remoteMethod === undefined || this.remoteParameterOptionsLoading) {
|
||||
if (this.node === null || this.hasRemoteMethod === false || this.remoteParameterOptionsLoading) {
|
||||
return;
|
||||
}
|
||||
this.remoteParameterOptionsLoadingIssues = null;
|
||||
|
@ -602,7 +604,10 @@ export default mixins(
|
|||
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
|
||||
|
||||
try {
|
||||
const options = await this.restApi().getNodeParameterOptions({name: this.node.type, version: this.node.typeVersion}, this.path, this.remoteMethod, resolvedNodeParameters, this.node.credentials);
|
||||
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
|
||||
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined;
|
||||
|
||||
const options = await this.restApi().getNodeParameterOptions({ nodeTypeAndVersion: { name: this.node.type, version: this.node.typeVersion}, path: this.path, methodName: loadOptionsMethod, loadOptions, currentNodeParameters: resolvedNodeParameters, credentials: this.node.credentials });
|
||||
this.remoteParameterOptions.push.apply(this.remoteParameterOptions, options);
|
||||
} catch (error) {
|
||||
this.remoteParameterOptionsLoadingIssues = error.message;
|
||||
|
@ -771,7 +776,7 @@ export default mixins(
|
|||
}
|
||||
}
|
||||
|
||||
if (this.remoteMethod !== undefined && this.node !== null) {
|
||||
if (this.hasRemoteMethod === true && this.node !== null) {
|
||||
// Make sure to load the parameter options
|
||||
// directly and whenever the credentials change
|
||||
this.$watch(() => this.node!.credentials, () => {
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from '@/Interface';
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptions,
|
||||
INodeCredentials,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
|
@ -97,14 +98,7 @@ export const restApi = Vue.extend({
|
|||
},
|
||||
|
||||
// Returns all the parameter options from the server
|
||||
getNodeParameterOptions: (nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]> => {
|
||||
const sendData = {
|
||||
nodeTypeAndVersion,
|
||||
path,
|
||||
methodName,
|
||||
credentials,
|
||||
currentNodeParameters,
|
||||
};
|
||||
getNodeParameterOptions: (sendData: { nodeTypeAndVersion: INodeTypeNameVersion, path: string, methodName?: string, loadOptions?: ILoadOptions, currentNodeParameters: INodeParameters, credentials?: INodeCredentials }): Promise<INodePropertyOptions[]> => {
|
||||
return self.restApi().makeRestApiRequest('GET', '/node-parameter-options', sendData);
|
||||
},
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
import {
|
||||
ICredentialType,
|
||||
ICredentialsDecrypted,
|
||||
NodeCredentialTestResult,
|
||||
INodeCredentialTestResult,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { getAppNameFromCredType } from '@/components/helpers';
|
||||
|
@ -158,7 +158,7 @@ const module: Module<ICredentialsState, IRootState> = {
|
|||
oAuth1Authorize: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsResponse) => {
|
||||
return oAuth1CredentialAuthorize(context.rootGetters.getRestApiContext, data);
|
||||
},
|
||||
testCredential: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsDecrypted): Promise<NodeCredentialTestResult> => {
|
||||
testCredential: async (context: ActionContext<ICredentialsState, IRootState>, data: ICredentialsDecrypted): Promise<INodeCredentialTestResult> => {
|
||||
return testCredential(context.rootGetters.getRestApiContext, { credentials: data });
|
||||
},
|
||||
getNewCredentialName: async (context: ActionContext<ICredentialsState, IRootState>, params: { credentialTypeName: string }) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2017"
|
||||
"es2019"
|
||||
],
|
||||
"types": [
|
||||
"node"
|
||||
|
@ -13,7 +13,7 @@
|
|||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist/",
|
||||
"target": "es2017",
|
||||
"target": "es2019",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
IAuthenticateBearer,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -15,4 +16,10 @@ export class AsanaApi implements ICredentialType {
|
|||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'bearer',
|
||||
properties: {},
|
||||
} as IAuthenticateBearer;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
IAuthenticateHeaderAuth,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -24,4 +25,11 @@ export class HttpHeaderAuth implements ICredentialType {
|
|||
default: '',
|
||||
},
|
||||
];
|
||||
authenticate = {
|
||||
type: 'headerAuth',
|
||||
properties: {
|
||||
name: '={{credentials.name}}',
|
||||
value: '={{credentials.value}}',
|
||||
},
|
||||
} as IAuthenticateHeaderAuth;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialType,
|
||||
IHttpRequestOptions,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -22,4 +24,8 @@ export class MattermostApi implements ICredentialType {
|
|||
default: '',
|
||||
},
|
||||
];
|
||||
async authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
|
||||
requestOptions.headers!['Authorization'] = `Bearer ${credentials.accessToken}`;
|
||||
return requestOptions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
IAuthenticateQueryAuth,
|
||||
ICredentialTestRequest,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -16,4 +18,12 @@ export class PipedriveApi implements ICredentialType {
|
|||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate = {
|
||||
type: 'queryAuth',
|
||||
properties: {
|
||||
key: 'api_token',
|
||||
value: '={{$credentials.apiToken}}',
|
||||
},
|
||||
} as IAuthenticateQueryAuth;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
|
||||
import {
|
||||
IDataObject,
|
||||
IHttpRequestMethods,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
|
@ -51,6 +52,12 @@ export class Asana implements INodeType {
|
|||
],
|
||||
},
|
||||
},
|
||||
testedBy: {
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '/users/me',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'asanaOAuth2Api',
|
||||
|
@ -64,6 +71,10 @@ export class Asana implements INodeType {
|
|||
},
|
||||
},
|
||||
],
|
||||
requestDefaults: {
|
||||
baseURL: 'https://app.asana.com/api/1.0',
|
||||
url: '',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
|
@ -1834,7 +1845,7 @@ export class Asana implements INodeType {
|
|||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
let endpoint = '';
|
||||
let requestMethod = '';
|
||||
let requestMethod: IHttpRequestMethods = 'GET';
|
||||
|
||||
let body: IDataObject;
|
||||
let qs: IDataObject;
|
||||
|
|
|
@ -4,15 +4,11 @@ import {
|
|||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IHttpRequestMethods,
|
||||
IHttpRequestOptions,
|
||||
INodePropertyOptions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -28,39 +24,23 @@ import {
|
|||
* @param {object} body
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: object, uri?: string | undefined): Promise<any> { // tslint:disable-line:no-any
|
||||
const authenticationMethod = this.getNodeParameter('authentication', 0);
|
||||
export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body: object, query?: IDataObject, uri?: string | undefined): Promise<any> { // tslint:disable-line:no-any
|
||||
const authenticationMethod = this.getNodeParameter('authentication', 0) as string;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
const options: IHttpRequestOptions = {
|
||||
headers: {},
|
||||
method,
|
||||
body: { data: body },
|
||||
qs: query,
|
||||
uri: uri || `https://app.asana.com/api/1.0${endpoint}`,
|
||||
url: uri || `https://app.asana.com/api/1.0${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
try {
|
||||
if (authenticationMethod === 'accessToken') {
|
||||
const credentials = await this.getCredentials('asanaApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`;
|
||||
|
||||
return await this.helpers.request!(options);
|
||||
} else {
|
||||
//@ts-ignore
|
||||
return await this.helpers.requestOAuth2.call(this, 'asanaOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
const credentialType = authenticationMethod === 'accessToken' ? 'asanaApi' : 'asanaOAuth2Api';
|
||||
return this.helpers.requestWithAuthentication.call(this, credentialType, options);
|
||||
}
|
||||
|
||||
export async function asanaApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
export async function asanaApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -91,7 +91,7 @@ export class AwsTextract implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async awsTextractApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async awsTextractApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject, 'sts');
|
||||
} catch (error) {
|
||||
|
|
|
@ -3,10 +3,10 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IHttpRequestOptions,
|
||||
NodeCredentialTestResult,
|
||||
INodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function bambooHrApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
export async function bambooHrApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
|
@ -19,7 +19,7 @@ export async function bambooHrApiCredentialTest(this: ICredentialTestFunctions,
|
|||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
} as NodeCredentialTestResult;
|
||||
} as INodeCredentialTestResult;
|
||||
}
|
||||
|
||||
async function validateCredentials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject): Promise<any> { // tslint:disable-line:no-any
|
||||
|
@ -43,4 +43,4 @@ async function validateCredentials(this: ICredentialTestFunctions, decryptedCred
|
|||
};
|
||||
|
||||
return await this.helpers.request(options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,11 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -153,7 +153,7 @@ export class BitbucketTrigger implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async bitbucketApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async bitbucketApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
|
|
|
@ -7,10 +7,10 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -101,7 +101,7 @@ export class Dhl implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async dhlApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async dhlApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
|
|
|
@ -7,11 +7,11 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -268,7 +268,7 @@ export class Dropcontact implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async dropcontactApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async dropcontactApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
|
|
|
@ -7,11 +7,11 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -121,7 +121,7 @@ export class ElasticSecurity implements INodeType {
|
|||
async elasticSecurityApiTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<NodeCredentialTestResult> {
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const {
|
||||
username,
|
||||
password,
|
||||
|
|
|
@ -8,10 +8,10 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -1738,7 +1738,7 @@ export class Github implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async githubApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async githubApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
const baseUrl = credentials!.server as string || 'https://api.github.com/user';
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -1017,7 +1017,7 @@ export class GoogleSheets implements INodeType {
|
|||
},
|
||||
},
|
||||
credentialTest: {
|
||||
async googleApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async googleApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
const tokenRequest = await getAccessToken.call(this, credential.data! as unknown as IGoogleAuthCredentials);
|
||||
if (!tokenRequest.access_token) {
|
||||
|
|
|
@ -7,12 +7,12 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -134,7 +134,7 @@ export class Grafana implements INodeType {
|
|||
async grafanaApiTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<NodeCredentialTestResult> {
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const { apiKey, baseUrl: rawBaseUrl } = credential.data as GrafanaCredentials;
|
||||
const baseUrl = tolerateTrailingSlash(rawBaseUrl);
|
||||
|
||||
|
@ -573,4 +573,4 @@ export class Grafana implements INodeType {
|
|||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -80,7 +80,7 @@ export class Grist implements INodeType {
|
|||
async gristApiTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<NodeCredentialTestResult> {
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const {
|
||||
apiKey,
|
||||
planType,
|
||||
|
|
|
@ -7,11 +7,11 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -142,7 +142,7 @@ export class HomeAssistant implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async homeAssistantApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async homeAssistantApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
const options = {
|
||||
method: 'GET',
|
||||
|
|
|
@ -7,12 +7,12 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -438,7 +438,7 @@ export class Jenkins implements INodeType {
|
|||
async jenkinApiCredentialTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<NodeCredentialTestResult> {
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const { baseUrl, username, apiKey } = credential.data as JenkinsApiCredentials;
|
||||
|
||||
const url = tolerateTrailingSlash(baseUrl);
|
||||
|
|
|
@ -13,11 +13,11 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -154,7 +154,7 @@ export class Jira implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async jiraSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async jiraSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
const data = Buffer.from(`${credentials!.email}:${credentials!.password || credentials!.apiToken}`).toString('base64');
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ import {
|
|||
import {
|
||||
GenericValue,
|
||||
IDataObject,
|
||||
IHttpRequestMethods,
|
||||
IHttpRequestOptions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
*/
|
||||
export async function apiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD',
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
body: IDataObject | GenericValue | GenericValue[] = {},
|
||||
query: IDataObject = {},
|
||||
|
@ -34,16 +34,11 @@ export async function apiRequest(
|
|||
qs: query,
|
||||
url: `${credentials.baseUrl}/api/v4/${endpoint}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${credentials.accessToken}`,
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
return await this.helpers.httpRequest(options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
return this.helpers.httpRequestWithAuthentication.call(this, 'mattermostApi', options);
|
||||
}
|
||||
|
||||
export async function apiRequestAllItems(
|
||||
|
|
|
@ -8,13 +8,13 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -194,7 +194,7 @@ export class NotionV2 implements INodeType {
|
|||
},
|
||||
},
|
||||
credentialTest: {
|
||||
async notionApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async notionApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
|
@ -67,23 +67,9 @@ export async function pipedriveApiRequest(this: IHookFunctions | IExecuteFunctio
|
|||
query = {};
|
||||
}
|
||||
|
||||
let responseData;
|
||||
|
||||
try {
|
||||
if (authenticationMethod === 'basicAuth' || authenticationMethod === 'apiToken' || authenticationMethod === 'none') {
|
||||
|
||||
const credentials = await this.getCredentials('pipedriveApi');
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
query.api_token = credentials.apiToken;
|
||||
//@ts-ignore
|
||||
responseData = await this.helpers.request(options);
|
||||
|
||||
} else {
|
||||
responseData = await this.helpers.requestOAuth2!.call(this, 'pipedriveOAuth2Api', options);
|
||||
}
|
||||
const credentialType = authenticationMethod === 'apiToken' ? 'pipedriveApi' : 'pipedriveOAuth2Api';
|
||||
const responseData = await this.helpers.requestWithAuthentication.call(this, credentialType, options);
|
||||
|
||||
if (downloadFile === true) {
|
||||
return {
|
||||
|
|
|
@ -73,6 +73,12 @@ export class Pipedrive implements INodeType {
|
|||
],
|
||||
},
|
||||
},
|
||||
testedBy: {
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '/users/me',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'pipedriveOAuth2Api',
|
||||
|
@ -86,6 +92,10 @@ export class Pipedrive implements INodeType {
|
|||
},
|
||||
},
|
||||
],
|
||||
requestDefaults: {
|
||||
baseURL: 'https://api.pipedrive.com/v1',
|
||||
url: '',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
|
|
|
@ -5,11 +5,11 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -288,7 +288,7 @@ export class Slack implements INodeType {
|
|||
},
|
||||
},
|
||||
credentialTest: {
|
||||
async testSlackTokenAuth(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async testSlackTokenAuth(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
|
|
|
@ -7,10 +7,10 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -124,7 +124,7 @@ export class Splunk implements INodeType {
|
|||
async splunkApiTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<NodeCredentialTestResult> {
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const {
|
||||
authToken,
|
||||
baseUrl,
|
||||
|
|
|
@ -8,11 +8,11 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -104,7 +104,7 @@ export class Supabase implements INodeType {
|
|||
},
|
||||
},
|
||||
credentialTest: {
|
||||
async supabaseApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async supabaseApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCrendentials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
|
|
|
@ -6,10 +6,10 @@ import {
|
|||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { versionDescription } from './actions/versionDescription';
|
||||
|
@ -31,7 +31,7 @@ export class SyncroMspV1 implements INodeType {
|
|||
methods = {
|
||||
loadOptions,
|
||||
credentialTest: {
|
||||
async syncroMspApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async syncroMspApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
|
|
|
@ -7,10 +7,10 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -1815,7 +1815,7 @@ export class Telegram implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async telegramBotTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async telegramBotTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
const options = {
|
||||
uri: `https://api.telegram.org/bot${credentials!.accessToken}/getMe`,
|
||||
|
|
|
@ -7,12 +7,12 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
JsonObject,
|
||||
NodeApiError,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -122,7 +122,7 @@ export class TypeformTrigger implements INodeType {
|
|||
getForms,
|
||||
},
|
||||
credentialTest: {
|
||||
async testTypeformTokenAuth(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async testTypeformTokenAuth(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
|
||||
const options = {
|
||||
|
|
|
@ -6,10 +6,10 @@ import {
|
|||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -73,7 +73,7 @@ export class UrlScanIo implements INodeType {
|
|||
async urlScanIoApiTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credentials: ICredentialsDecrypted,
|
||||
): Promise<NodeCredentialTestResult> {
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const { apiKey } = credentials.data as { apiKey: string };
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
|
|
|
@ -11,12 +11,12 @@ import {
|
|||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeCredentialTestResult,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -154,7 +154,7 @@ export class Zendesk implements INodeType {
|
|||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async zendeskSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
async zendeskSoftwareApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
const subdomain = credentials!.subdomain;
|
||||
const email = credentials!.email;
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2017",
|
||||
"es2019.array"
|
||||
"es2019"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
|
@ -16,7 +15,7 @@
|
|||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist/",
|
||||
"target": "es2017",
|
||||
"target": "es2019",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.merge": "^4.6.6",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/xml2js": "^0.4.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
||||
|
@ -48,6 +49,8 @@
|
|||
"dependencies": {
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"riot-tmpl": "^3.0.8",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
|
|
|
@ -12,8 +12,14 @@ import { WorkflowHooks } from './WorkflowHooks';
|
|||
import { WorkflowOperationError } from './WorkflowErrors';
|
||||
import { NodeApiError, NodeOperationError } from './NodeErrors';
|
||||
|
||||
export interface IAdditionalCredentialOptions {
|
||||
oauth2?: IOAuth2Options;
|
||||
credentialsDecrypted?: ICredentialsDecrypted;
|
||||
}
|
||||
|
||||
export type IAllExecuteFunctions =
|
||||
| IExecuteFunctions
|
||||
| IExecutePaginationFunctions
|
||||
| IExecuteSingleFunctions
|
||||
| IHookFunctions
|
||||
| ILoadOptionsFunctions
|
||||
|
@ -128,6 +134,17 @@ export interface ICredentialsExpressionResolveValues {
|
|||
workflow: Workflow;
|
||||
}
|
||||
|
||||
// Simplified options of request library
|
||||
export interface IRequestOptionsSimplified {
|
||||
auth?: {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
body: IDataObject;
|
||||
headers: IDataObject;
|
||||
qs: IDataObject;
|
||||
}
|
||||
|
||||
export abstract class ICredentialsHelper {
|
||||
encryptionKey: string;
|
||||
|
||||
|
@ -135,6 +152,16 @@ export abstract class ICredentialsHelper {
|
|||
this.encryptionKey = encryptionKey;
|
||||
}
|
||||
|
||||
abstract getParentTypes(name: string): string[];
|
||||
|
||||
abstract authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
requestOptions: IHttpRequestOptions | IRequestOptionsSimplified,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
): Promise<IHttpRequestOptions>;
|
||||
|
||||
abstract getCredentials(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
|
@ -155,6 +182,80 @@ export abstract class ICredentialsHelper {
|
|||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAuthenticateBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateBasicAuth extends IAuthenticateBase {
|
||||
type: 'basicAuth';
|
||||
properties: {
|
||||
userPropertyName?: string;
|
||||
passwordPropertyName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateBearer extends IAuthenticateBase {
|
||||
type: 'bearer';
|
||||
properties: {
|
||||
tokenPropertyName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateHeaderAuth extends IAuthenticateBase {
|
||||
type: 'headerAuth';
|
||||
properties: {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthenticateQueryAuth extends IAuthenticateBase {
|
||||
type: 'queryAuth';
|
||||
properties: {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type IAuthenticate =
|
||||
| ((
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
) => Promise<IHttpRequestOptions>)
|
||||
| IAuthenticateBasicAuth
|
||||
| IAuthenticateBearer
|
||||
| IAuthenticateHeaderAuth
|
||||
| IAuthenticateQueryAuth;
|
||||
|
||||
export interface IAuthenticateRuleBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface IAuthenticateRuleResponseCode extends IAuthenticateRuleBase {
|
||||
type: 'responseCode';
|
||||
properties: {
|
||||
value: number;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICredentialTestRequest {
|
||||
request: IHttpRequestOptions;
|
||||
rules?: IAuthenticateRuleResponseCode[];
|
||||
}
|
||||
|
||||
export interface ICredentialTestRequestData {
|
||||
nodeType?: INodeType;
|
||||
testRequest: ICredentialTestRequest;
|
||||
}
|
||||
|
||||
export interface ICredentialType {
|
||||
name: string;
|
||||
displayName: string;
|
||||
|
@ -163,13 +264,13 @@ export interface ICredentialType {
|
|||
properties: INodeProperties[];
|
||||
documentationUrl?: string;
|
||||
__overwrittenProperties?: string[];
|
||||
authenticate?: IAuthenticate;
|
||||
test?: ICredentialTestRequest;
|
||||
}
|
||||
|
||||
export interface ICredentialTypes {
|
||||
credentialTypes?: {
|
||||
[key: string]: ICredentialType;
|
||||
};
|
||||
init(credentialTypes?: { [key: string]: ICredentialType }): Promise<void>;
|
||||
credentialTypes?: ICredentialTypeData;
|
||||
init(credentialTypes?: ICredentialTypeData): Promise<void>;
|
||||
getAll(): ICredentialType[];
|
||||
getByName(credentialType: string): ICredentialType;
|
||||
}
|
||||
|
@ -301,10 +402,13 @@ export interface IExecuteContextData {
|
|||
[key: string]: IContextObject;
|
||||
}
|
||||
|
||||
export type IHttpRequestMethods = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT';
|
||||
|
||||
export interface IHttpRequestOptions {
|
||||
url: string;
|
||||
baseURL?: string;
|
||||
headers?: IDataObject;
|
||||
method?: 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT';
|
||||
method?: IHttpRequestMethods;
|
||||
body?: FormData | GenericValue | GenericValue[] | Buffer | URLSearchParams;
|
||||
qs?: IDataObject;
|
||||
arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma';
|
||||
|
@ -338,6 +442,33 @@ export interface IN8nHttpFullResponse {
|
|||
statusMessage?: string;
|
||||
}
|
||||
|
||||
export interface IN8nRequestOperations {
|
||||
pagination?:
|
||||
| IN8nRequestOperationPaginationOffset
|
||||
| ((
|
||||
this: IExecutePaginationFunctions,
|
||||
requestOptions: IRequestOptionsFromParameters,
|
||||
) => Promise<INodeExecutionData[]>);
|
||||
}
|
||||
|
||||
export interface IN8nRequestOperationPaginationBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IN8nRequestOperationPaginationOffset extends IN8nRequestOperationPaginationBase {
|
||||
type: 'offset';
|
||||
properties: {
|
||||
limitParameter: string;
|
||||
offsetParameter: string;
|
||||
pageSize: number;
|
||||
rootProperty?: string; // Optional Path to option array
|
||||
type: 'body' | 'query';
|
||||
};
|
||||
}
|
||||
|
||||
export interface IExecuteFunctions {
|
||||
continueOnFail(): boolean;
|
||||
evaluateExpression(
|
||||
|
@ -382,6 +513,12 @@ export interface IExecuteFunctions {
|
|||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
@ -410,15 +547,32 @@ export interface IExecuteSingleFunctions {
|
|||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
||||
export interface IExecutePaginationFunctions extends IExecuteSingleFunctions {
|
||||
makeRoutingRequest(
|
||||
this: IAllExecuteFunctions,
|
||||
requestOptions: IRequestOptionsFromParameters,
|
||||
): Promise<INodeExecutionData[]>;
|
||||
}
|
||||
export interface IExecuteWorkflowInfo {
|
||||
code?: IWorkflowBase;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export type ICredentialTestFunction = (
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
) => Promise<INodeCredentialTestResult>;
|
||||
|
||||
export interface ICredentialTestFunctions {
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any;
|
||||
|
@ -448,6 +602,20 @@ export interface ILoadOptionsFunctions {
|
|||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
// TODO: Remove from here. Add it only now to LoadOptions as many nodes do import
|
||||
// from n8n-workflow instead of n8n-core
|
||||
requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: any, // tslint:disable-line:no-any
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: ((...args: any[]) => any) | undefined; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
@ -471,6 +639,12 @@ export interface IHookFunctions {
|
|||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
@ -493,6 +667,12 @@ export interface IPollFunctions {
|
|||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
@ -518,6 +698,12 @@ export interface ITriggerFunctions {
|
|||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
@ -549,6 +735,12 @@ export interface IWebhookFunctions {
|
|||
httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<IN8nHttpResponse | IN8nHttpFullResponse>;
|
||||
[key: string]: (...args: any[]) => any; // tslint:disable-line:no-any
|
||||
};
|
||||
}
|
||||
|
@ -636,12 +828,21 @@ export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
|||
|
||||
export type EditorTypes = 'code' | 'json';
|
||||
|
||||
export interface ILoadOptions {
|
||||
routing?: {
|
||||
operations?: IN8nRequestOperations;
|
||||
output?: INodeRequestOutput;
|
||||
request?: IHttpRequestOptionsFromParameters;
|
||||
};
|
||||
}
|
||||
|
||||
export interface INodePropertyTypeOptions {
|
||||
alwaysOpenEditWindow?: boolean; // Supported by: string
|
||||
codeAutocomplete?: CodeAutocompleteTypes; // Supported by: string
|
||||
editor?: EditorTypes; // Supported by: string
|
||||
loadOptionsDependsOn?: string[]; // Supported by: options
|
||||
loadOptionsMethod?: string; // Supported by: options
|
||||
loadOptions?: ILoadOptions; // Supported by: options
|
||||
maxValue?: number; // Supported by: number
|
||||
minValue?: number; // Supported by: number
|
||||
multipleValues?: boolean; // Supported by: <All>
|
||||
|
@ -651,7 +852,7 @@ export interface INodePropertyTypeOptions {
|
|||
rows?: number; // Supported by: string
|
||||
showAlpha?: boolean; // Supported by: color
|
||||
sortable?: boolean; // Supported when "multipleValues" set to true
|
||||
[key: string]: boolean | number | string | EditorTypes | undefined | string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface IDisplayOptions {
|
||||
|
@ -677,11 +878,13 @@ export interface INodeProperties {
|
|||
isNodeSetting?: boolean;
|
||||
noDataExpression?: boolean;
|
||||
required?: boolean;
|
||||
routing?: INodePropertyRouting;
|
||||
}
|
||||
export interface INodePropertyOptions {
|
||||
name: string;
|
||||
value: string | number | boolean;
|
||||
description?: string;
|
||||
routing?: INodePropertyRouting;
|
||||
}
|
||||
|
||||
export interface INodePropertyCollection {
|
||||
|
@ -723,10 +926,7 @@ export interface INodeType {
|
|||
};
|
||||
credentialTest?: {
|
||||
// Contains a group of functins that test credentials.
|
||||
[functionName: string]: (
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
) => Promise<NodeCredentialTestResult>;
|
||||
[functionName: string]: ICredentialTestFunction;
|
||||
};
|
||||
};
|
||||
webhookMethods?: {
|
||||
|
@ -742,12 +942,12 @@ export interface INodeVersionedType {
|
|||
description: INodeTypeBaseDescription;
|
||||
getNodeType: (version?: number) => INodeType;
|
||||
}
|
||||
export interface NodeCredentialTestResult {
|
||||
export interface INodeCredentialTestResult {
|
||||
status: 'OK' | 'Error';
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface NodeCredentialTestRequest {
|
||||
export interface INodeCredentialTestRequest {
|
||||
nodeToTestWith?: string; // node name i.e. slack
|
||||
credentials: ICredentialsDecrypted;
|
||||
}
|
||||
|
@ -765,7 +965,7 @@ export interface INodeCredentialDescription {
|
|||
name: string;
|
||||
required?: boolean;
|
||||
displayOptions?: IDisplayOptions;
|
||||
testedBy?: string; // Name of a function inside `loadOptions.credentialTest`
|
||||
testedBy?: ICredentialTestRequest | string; // Name of a function inside `loadOptions.credentialTest`
|
||||
}
|
||||
|
||||
export type INodeIssueTypes = 'credentials' | 'execution' | 'parameters' | 'typeUnknown';
|
||||
|
@ -804,6 +1004,105 @@ export interface INodeTypeBaseDescription {
|
|||
codex?: CodexData;
|
||||
}
|
||||
|
||||
export interface INodePropertyRouting {
|
||||
operations?: IN8nRequestOperations; // Should be changed, does not sound right
|
||||
output?: INodeRequestOutput;
|
||||
request?: IHttpRequestOptionsFromParameters;
|
||||
send?: INodeRequestSend;
|
||||
}
|
||||
|
||||
export type PostReceiveAction =
|
||||
| ((
|
||||
this: IExecuteSingleFunctions,
|
||||
items: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
) => Promise<INodeExecutionData[]>)
|
||||
| IPostReceiveBinaryData
|
||||
| IPostReceiveRootProperty
|
||||
| IPostReceiveSet
|
||||
| IPostReceiveSetKeyValue
|
||||
| IPostReceiveSort;
|
||||
|
||||
export type PreSendAction = (
|
||||
this: IExecuteSingleFunctions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
) => Promise<IHttpRequestOptions>;
|
||||
|
||||
export interface INodeRequestOutput {
|
||||
maxResults?: number | string;
|
||||
postReceive?: PostReceiveAction[];
|
||||
}
|
||||
|
||||
export interface INodeRequestSend {
|
||||
preSend?: PreSendAction[];
|
||||
paginate?: boolean | string; // Where should this life?
|
||||
property?: string; // Maybe: propertyName, destinationProperty?
|
||||
propertyInDotNotation?: boolean; // Enabled by default
|
||||
type?: 'body' | 'query';
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface IPostReceiveBase {
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: string | number | IDataObject;
|
||||
};
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface IPostReceiveBinaryData extends IPostReceiveBase {
|
||||
type: 'binaryData';
|
||||
properties: {
|
||||
destinationProperty: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveRootProperty extends IPostReceiveBase {
|
||||
type: 'rootProperty';
|
||||
properties: {
|
||||
property: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveSet extends IPostReceiveBase {
|
||||
type: 'set';
|
||||
properties: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveSetKeyValue extends IPostReceiveBase {
|
||||
type: 'setKeyValue';
|
||||
properties: {
|
||||
[key: string]: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPostReceiveSort extends IPostReceiveBase {
|
||||
type: 'sort';
|
||||
properties: {
|
||||
key: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IHttpRequestOptionsFromParameters extends Partial<IHttpRequestOptions> {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface IRequestOptionsFromParameters {
|
||||
maxResults?: number | string;
|
||||
options: IHttpRequestOptionsFromParameters;
|
||||
paginate?: boolean | string;
|
||||
preSend: PreSendAction[];
|
||||
postReceive: Array<{
|
||||
data: {
|
||||
parameterValue: string | IDataObject | undefined;
|
||||
};
|
||||
actions: PostReceiveAction[];
|
||||
}>;
|
||||
requestOperations?: IN8nRequestOperations;
|
||||
}
|
||||
|
||||
export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||
version: number;
|
||||
defaults: INodeParameters;
|
||||
|
@ -817,6 +1116,8 @@ export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
|||
credentials?: INodeCredentialDescription[];
|
||||
maxNodes?: number; // How many nodes of that type can be created in a workflow
|
||||
polling?: boolean;
|
||||
requestDefaults?: IHttpRequestOptionsFromParameters;
|
||||
requestOperations?: IN8nRequestOperations;
|
||||
hooks?: {
|
||||
[key: string]: INodeHookDescription[] | undefined;
|
||||
activate?: INodeHookDescription[];
|
||||
|
@ -869,9 +1170,7 @@ export interface IWorkflowDataProxyData {
|
|||
$workflow: any;
|
||||
}
|
||||
|
||||
export interface IWorkflowDataProxyAdditionalKeys {
|
||||
[key: string]: string | number | undefined;
|
||||
}
|
||||
export type IWorkflowDataProxyAdditionalKeys = IDataObject;
|
||||
|
||||
export interface IWorkflowMetadata {
|
||||
id?: number | string;
|
||||
|
@ -898,6 +1197,13 @@ export interface INodeTypes {
|
|||
getByNameAndVersion(nodeType: string, version?: number): INodeType | undefined;
|
||||
}
|
||||
|
||||
export interface ICredentialTypeData {
|
||||
[key: string]: {
|
||||
type: ICredentialType;
|
||||
sourcePath: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface INodeTypeData {
|
||||
[key: string]: {
|
||||
type: INodeType | INodeVersionedType;
|
||||
|
|
814
packages/workflow/src/RoutingNode.ts
Normal file
814
packages/workflow/src/RoutingNode.ts
Normal file
|
@ -0,0 +1,814 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable import/no-cycle */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-continue */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import get from 'lodash.get';
|
||||
import merge from 'lodash.merge';
|
||||
import set from 'lodash.set';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
IHttpRequestOptions,
|
||||
IN8nHttpFullResponse,
|
||||
INode,
|
||||
INodeExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
IRequestOptionsFromParameters,
|
||||
IRunExecutionData,
|
||||
ITaskDataConnections,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from '.';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IExecuteSingleFunctions,
|
||||
IN8nRequestOperations,
|
||||
INodeProperties,
|
||||
INodePropertyCollection,
|
||||
PostReceiveAction,
|
||||
} from './Interfaces';
|
||||
|
||||
export class RoutingNode {
|
||||
additionalData: IWorkflowExecuteAdditionalData;
|
||||
|
||||
connectionInputData: INodeExecutionData[];
|
||||
|
||||
node: INode;
|
||||
|
||||
mode: WorkflowExecuteMode;
|
||||
|
||||
runExecutionData: IRunExecutionData;
|
||||
|
||||
workflow: Workflow;
|
||||
|
||||
constructor(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
runExecutionData: IRunExecutionData,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
) {
|
||||
this.additionalData = additionalData;
|
||||
this.connectionInputData = connectionInputData;
|
||||
this.runExecutionData = runExecutionData;
|
||||
this.mode = mode;
|
||||
this.node = node;
|
||||
this.workflow = workflow;
|
||||
}
|
||||
|
||||
async runNode(
|
||||
inputData: ITaskDataConnections,
|
||||
runIndex: number,
|
||||
nodeType: INodeType,
|
||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||
credentialsDecrypted?: ICredentialsDecrypted,
|
||||
): Promise<INodeExecutionData[][] | null | undefined> {
|
||||
const items = inputData.main[0] as INodeExecutionData[];
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
let responseData;
|
||||
|
||||
let credentialType: string | undefined;
|
||||
|
||||
if (nodeType.description.credentials?.length) {
|
||||
credentialType = nodeType.description.credentials[0].name;
|
||||
}
|
||||
const executeFunctions = nodeExecuteFunctions.getExecuteFunctions(
|
||||
this.workflow,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.connectionInputData,
|
||||
inputData,
|
||||
this.node,
|
||||
this.additionalData,
|
||||
this.mode,
|
||||
);
|
||||
|
||||
let credentials: ICredentialDataDecryptedObject | undefined;
|
||||
if (credentialsDecrypted) {
|
||||
credentials = credentialsDecrypted.data;
|
||||
} else if (credentialType) {
|
||||
credentials = (await executeFunctions.getCredentials(credentialType)) || {};
|
||||
}
|
||||
|
||||
// TODO: Think about how batching could be handled for REST APIs which support it
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
|
||||
this.workflow,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.connectionInputData,
|
||||
inputData,
|
||||
this.node,
|
||||
i,
|
||||
this.additionalData,
|
||||
this.mode,
|
||||
);
|
||||
|
||||
const requestData: IRequestOptionsFromParameters = {
|
||||
options: {
|
||||
qs: {},
|
||||
body: {},
|
||||
},
|
||||
preSend: [],
|
||||
postReceive: [],
|
||||
requestOperations: {},
|
||||
};
|
||||
|
||||
if (nodeType.description.requestOperations) {
|
||||
requestData.requestOperations = { ...nodeType.description.requestOperations };
|
||||
}
|
||||
|
||||
if (nodeType.description.requestDefaults) {
|
||||
for (const key of Object.keys(nodeType.description.requestDefaults)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let value = (nodeType.description.requestDefaults as Record<string, any>)[key];
|
||||
// If the value is an expression resolve it
|
||||
value = this.getParameterValue(
|
||||
value,
|
||||
i,
|
||||
runIndex,
|
||||
{ $credentials: credentials },
|
||||
true,
|
||||
) as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(requestData.options as Record<string, any>)[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const property of nodeType.description.properties) {
|
||||
let value = get(this.node.parameters, property.name, []) as string | NodeParameterValue;
|
||||
// If the value is an expression resolve it
|
||||
value = this.getParameterValue(
|
||||
value,
|
||||
i,
|
||||
runIndex,
|
||||
{ $credentials: credentials },
|
||||
true,
|
||||
) as string | NodeParameterValue;
|
||||
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
thisArgs,
|
||||
property,
|
||||
i,
|
||||
runIndex,
|
||||
'',
|
||||
{ $credentials: credentials, $value: value },
|
||||
);
|
||||
|
||||
this.mergeOptions(requestData, tempOptions);
|
||||
}
|
||||
|
||||
// TODO: Change to handle some requests in parallel (should be configurable)
|
||||
responseData = await this.makeRoutingRequest(
|
||||
requestData,
|
||||
thisArgs,
|
||||
i,
|
||||
runIndex,
|
||||
credentialType,
|
||||
requestData.requestOperations,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
|
||||
if (requestData.maxResults) {
|
||||
// Remove not needed items in case APIs return to many
|
||||
responseData.splice(requestData.maxResults as number);
|
||||
}
|
||||
|
||||
returnData.push(...responseData);
|
||||
} catch (error) {
|
||||
if (get(this.node, 'continueOnFail', false)) {
|
||||
returnData.push({ json: {}, error: error.message });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
|
||||
mergeOptions(
|
||||
destinationOptions: IRequestOptionsFromParameters,
|
||||
sourceOptions?: IRequestOptionsFromParameters,
|
||||
): void {
|
||||
if (sourceOptions) {
|
||||
destinationOptions.paginate = destinationOptions.paginate ?? sourceOptions.paginate;
|
||||
destinationOptions.maxResults = sourceOptions.maxResults
|
||||
? sourceOptions.maxResults
|
||||
: destinationOptions.maxResults;
|
||||
merge(destinationOptions.options, sourceOptions.options);
|
||||
destinationOptions.preSend.push(...sourceOptions.preSend);
|
||||
destinationOptions.postReceive.push(...sourceOptions.postReceive);
|
||||
if (sourceOptions.requestOperations) {
|
||||
destinationOptions.requestOperations = Object.assign(
|
||||
destinationOptions.requestOperations,
|
||||
sourceOptions.requestOperations,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async runPostReceiveAction(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
action: PostReceiveAction,
|
||||
inputData: INodeExecutionData[],
|
||||
responseData: IN8nHttpFullResponse,
|
||||
parameterValue: string | IDataObject | undefined,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
if (typeof action === 'function') {
|
||||
return action.call(executeSingleFunctions, inputData, responseData);
|
||||
}
|
||||
if (action.type === 'rootProperty') {
|
||||
try {
|
||||
return inputData.flatMap((item) => {
|
||||
// let itemContent = item.json[action.properties.property];
|
||||
let itemContent = get(item.json, action.properties.property);
|
||||
|
||||
if (!Array.isArray(itemContent)) {
|
||||
itemContent = [itemContent];
|
||||
}
|
||||
return (itemContent as IDataObject[]).map((json) => {
|
||||
return {
|
||||
json,
|
||||
};
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`The rootProperty "${action.properties.property}" could not be found on item.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (action.type === 'set') {
|
||||
const { value } = action.properties;
|
||||
// If the value is an expression resolve it
|
||||
return [
|
||||
{
|
||||
json: this.getParameterValue(
|
||||
value,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ $response: responseData, $value: parameterValue },
|
||||
false,
|
||||
) as IDataObject,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (action.type === 'sort') {
|
||||
// Sort the returned options
|
||||
const sortKey = action.properties.key;
|
||||
inputData.sort((a, b) => {
|
||||
const aSortValue = a.json[sortKey]
|
||||
? (a.json[sortKey]?.toString().toLowerCase() as string)
|
||||
: '';
|
||||
const bSortValue = b.json[sortKey]
|
||||
? (b.json[sortKey]?.toString().toLowerCase() as string)
|
||||
: '';
|
||||
if (aSortValue < bSortValue) {
|
||||
return -1;
|
||||
}
|
||||
if (aSortValue > bSortValue) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return inputData;
|
||||
}
|
||||
if (action.type === 'setKeyValue') {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
inputData.forEach((item) => {
|
||||
const returnItem: IDataObject = {};
|
||||
for (const key of Object.keys(action.properties)) {
|
||||
let propertyValue = (
|
||||
action.properties as Record<
|
||||
string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
any
|
||||
>
|
||||
)[key];
|
||||
// If the value is an expression resolve it
|
||||
propertyValue = this.getParameterValue(
|
||||
propertyValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{
|
||||
$response: responseData,
|
||||
$responseItem: item.json,
|
||||
$value: parameterValue,
|
||||
},
|
||||
true,
|
||||
) as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(returnItem as Record<string, any>)[key] = propertyValue;
|
||||
}
|
||||
returnData.push({ json: returnItem });
|
||||
});
|
||||
|
||||
return returnData;
|
||||
}
|
||||
if (action.type === 'binaryData') {
|
||||
responseData.body = Buffer.from(responseData.body as string);
|
||||
let { destinationProperty } = action.properties;
|
||||
|
||||
destinationProperty = this.getParameterValue(
|
||||
destinationProperty,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ $response: responseData, $value: parameterValue },
|
||||
false,
|
||||
) as string;
|
||||
|
||||
const binaryData = await executeSingleFunctions.helpers.prepareBinaryData(responseData.body);
|
||||
|
||||
return inputData.map((item) => {
|
||||
if (typeof item.json === 'string') {
|
||||
// By default is probably the binary data as string set, in this case remove it
|
||||
item.json = {};
|
||||
}
|
||||
|
||||
item.binary = {
|
||||
[destinationProperty]: binaryData,
|
||||
};
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async rawRoutingRequest(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
requestData: IRequestOptionsFromParameters,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
credentialType?: string,
|
||||
credentialsDecrypted?: ICredentialsDecrypted,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let responseData: IN8nHttpFullResponse;
|
||||
requestData.options.returnFullResponse = true;
|
||||
|
||||
if (credentialType) {
|
||||
responseData = (await executeSingleFunctions.helpers.httpRequestWithAuthentication.call(
|
||||
executeSingleFunctions,
|
||||
credentialType,
|
||||
requestData.options as IHttpRequestOptions,
|
||||
{ credentialsDecrypted },
|
||||
)) as IN8nHttpFullResponse;
|
||||
} else {
|
||||
responseData = (await executeSingleFunctions.helpers.httpRequest(
|
||||
requestData.options as IHttpRequestOptions,
|
||||
)) as IN8nHttpFullResponse;
|
||||
}
|
||||
|
||||
let returnData: INodeExecutionData[] = [
|
||||
{
|
||||
json: responseData.body as IDataObject,
|
||||
},
|
||||
];
|
||||
|
||||
if (requestData.postReceive.length) {
|
||||
// If postReceive functionality got defined execute all of them
|
||||
for (const postReceiveMethod of requestData.postReceive) {
|
||||
for (const action of postReceiveMethod.actions) {
|
||||
returnData = await this.runPostReceiveAction(
|
||||
executeSingleFunctions,
|
||||
action,
|
||||
returnData,
|
||||
responseData,
|
||||
postReceiveMethod.data.parameterValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No postReceive functionality got defined so simply add data as it is
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (Array.isArray(responseData.body)) {
|
||||
returnData = responseData.body.map((json) => {
|
||||
return {
|
||||
json,
|
||||
} as INodeExecutionData;
|
||||
});
|
||||
} else {
|
||||
returnData[0].json = responseData.body as IDataObject;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
async makeRoutingRequest(
|
||||
requestData: IRequestOptionsFromParameters,
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
credentialType?: string,
|
||||
requestOperations?: IN8nRequestOperations,
|
||||
credentialsDecrypted?: ICredentialsDecrypted,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let responseData: INodeExecutionData[];
|
||||
for (const preSendMethod of requestData.preSend) {
|
||||
requestData.options = await preSendMethod.call(
|
||||
executeSingleFunctions,
|
||||
requestData.options as IHttpRequestOptions,
|
||||
);
|
||||
}
|
||||
|
||||
const executePaginationFunctions = {
|
||||
...executeSingleFunctions,
|
||||
makeRoutingRequest: async (requestOptions: IRequestOptionsFromParameters) => {
|
||||
return this.rawRoutingRequest(
|
||||
executeSingleFunctions,
|
||||
requestOptions,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
credentialType,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
if (requestData.paginate && requestOperations?.pagination) {
|
||||
// Has pagination
|
||||
|
||||
if (typeof requestOperations.pagination === 'function') {
|
||||
// Pagination via function
|
||||
responseData = await requestOperations.pagination.call(
|
||||
executePaginationFunctions,
|
||||
requestData,
|
||||
);
|
||||
} else {
|
||||
// Pagination via JSON properties
|
||||
const { properties } = requestOperations.pagination;
|
||||
responseData = [];
|
||||
if (!requestData.options.qs) {
|
||||
requestData.options.qs = {};
|
||||
}
|
||||
|
||||
// Different predefined pagination types
|
||||
if (requestOperations.pagination.type === 'offset') {
|
||||
const optionsType = properties.type === 'body' ? 'body' : 'qs';
|
||||
if (properties.type === 'body' && !requestData.options.body) {
|
||||
requestData.options.body = {};
|
||||
}
|
||||
|
||||
(requestData.options[optionsType] as IDataObject)[properties.limitParameter] =
|
||||
properties.pageSize;
|
||||
(requestData.options[optionsType] as IDataObject)[properties.offsetParameter] = 0;
|
||||
let tempResponseData: INodeExecutionData[];
|
||||
do {
|
||||
if (requestData?.maxResults) {
|
||||
// Only request as many results as needed
|
||||
const resultsMissing = (requestData?.maxResults as number) - responseData.length;
|
||||
if (resultsMissing < 1) {
|
||||
break;
|
||||
}
|
||||
(requestData.options[optionsType] as IDataObject)[properties.limitParameter] =
|
||||
Math.min(properties.pageSize, resultsMissing);
|
||||
}
|
||||
|
||||
tempResponseData = await this.rawRoutingRequest(
|
||||
executeSingleFunctions,
|
||||
requestData,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
credentialType,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
|
||||
(requestData.options[optionsType] as IDataObject)[properties.offsetParameter] =
|
||||
((requestData.options[optionsType] as IDataObject)[
|
||||
properties.offsetParameter
|
||||
] as number) + properties.pageSize;
|
||||
|
||||
if (properties.rootProperty) {
|
||||
const tempResponseValue = get(tempResponseData[0].json, properties.rootProperty) as
|
||||
| IDataObject[]
|
||||
| undefined;
|
||||
if (tempResponseValue === undefined) {
|
||||
throw new Error(
|
||||
`The rootProperty "${properties.rootProperty}" could not be found on item.`,
|
||||
);
|
||||
}
|
||||
|
||||
tempResponseData = tempResponseValue.map((item) => {
|
||||
return {
|
||||
json: item,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
responseData.push(...tempResponseData);
|
||||
} while (tempResponseData.length && tempResponseData.length === properties.pageSize);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No pagination
|
||||
responseData = await this.rawRoutingRequest(
|
||||
executeSingleFunctions,
|
||||
requestData,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
credentialType,
|
||||
credentialsDecrypted,
|
||||
);
|
||||
}
|
||||
return responseData;
|
||||
}
|
||||
|
||||
getParameterValue(
|
||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
||||
returnObjectAsString = false,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | string {
|
||||
if (typeof parameterValue === 'string' && parameterValue.charAt(0) === '=') {
|
||||
return this.workflow.expression.getParameterValue(
|
||||
parameterValue,
|
||||
this.runExecutionData ?? null,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
this.node.name,
|
||||
this.connectionInputData,
|
||||
this.mode,
|
||||
additionalKeys ?? {},
|
||||
returnObjectAsString,
|
||||
);
|
||||
}
|
||||
|
||||
return parameterValue;
|
||||
}
|
||||
|
||||
getRequestOptionsFromParameters(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
nodeProperties: INodeProperties | INodePropertyOptions,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
path: string,
|
||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
||||
): IRequestOptionsFromParameters | undefined {
|
||||
const returnData: IRequestOptionsFromParameters = {
|
||||
options: {
|
||||
qs: {},
|
||||
body: {},
|
||||
},
|
||||
preSend: [],
|
||||
postReceive: [],
|
||||
requestOperations: {},
|
||||
};
|
||||
let basePath = path ? `${path}.` : '';
|
||||
|
||||
if (!NodeHelpers.displayParameter(this.node.parameters, nodeProperties, this.node.parameters)) {
|
||||
return undefined;
|
||||
}
|
||||
if (nodeProperties.routing) {
|
||||
let parameterValue: string | undefined;
|
||||
if (basePath + nodeProperties.name && 'type' in nodeProperties) {
|
||||
parameterValue = executeSingleFunctions.getNodeParameter(
|
||||
basePath + nodeProperties.name,
|
||||
) as string;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.operations) {
|
||||
returnData.requestOperations = { ...nodeProperties.routing.operations };
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.request) {
|
||||
for (const key of Object.keys(nodeProperties.routing.request)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let propertyValue = (nodeProperties.routing.request as Record<string, any>)[key];
|
||||
// If the value is an expression resolve it
|
||||
propertyValue = this.getParameterValue(
|
||||
propertyValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: parameterValue },
|
||||
true,
|
||||
) as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(returnData.options as Record<string, any>)[key] = propertyValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send) {
|
||||
let propertyName = nodeProperties.routing.send.property;
|
||||
if (propertyName !== undefined) {
|
||||
// If the propertyName is an expression resolve it
|
||||
propertyName = this.getParameterValue(
|
||||
propertyName,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
additionalKeys,
|
||||
true,
|
||||
) as string;
|
||||
|
||||
let value = parameterValue;
|
||||
|
||||
if (nodeProperties.routing.send.value) {
|
||||
const valueString = nodeProperties.routing.send.value;
|
||||
// Special value got set
|
||||
// If the valueString is an expression resolve it
|
||||
value = this.getParameterValue(
|
||||
valueString,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: value },
|
||||
true,
|
||||
) as string;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send.type === 'body') {
|
||||
// Send in "body"
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (nodeProperties.routing.send.propertyInDotNotation === false) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(returnData.options.body as Record<string, any>)![propertyName] = value;
|
||||
} else {
|
||||
set(returnData.options.body as object, propertyName, value);
|
||||
}
|
||||
} else {
|
||||
// Send in "query"
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (nodeProperties.routing.send.propertyInDotNotation === false) {
|
||||
returnData.options.qs![propertyName] = value;
|
||||
} else {
|
||||
set(returnData.options.qs as object, propertyName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send.paginate !== undefined) {
|
||||
let paginateValue = nodeProperties.routing.send.paginate;
|
||||
if (typeof paginateValue === 'string' && paginateValue.charAt(0) === '=') {
|
||||
// If the propertyName is an expression resolve it
|
||||
paginateValue = this.getParameterValue(
|
||||
paginateValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: parameterValue },
|
||||
true,
|
||||
) as string;
|
||||
}
|
||||
|
||||
returnData.paginate = !!paginateValue;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.send.preSend) {
|
||||
returnData.preSend.push(...nodeProperties.routing.send.preSend);
|
||||
}
|
||||
}
|
||||
if (nodeProperties.routing.output) {
|
||||
if (nodeProperties.routing.output.maxResults !== undefined) {
|
||||
let maxResultsValue = nodeProperties.routing.output.maxResults;
|
||||
if (typeof maxResultsValue === 'string' && maxResultsValue.charAt(0) === '=') {
|
||||
// If the propertyName is an expression resolve it
|
||||
maxResultsValue = this.getParameterValue(
|
||||
maxResultsValue,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
{ ...additionalKeys, $value: parameterValue },
|
||||
true,
|
||||
) as string;
|
||||
}
|
||||
|
||||
returnData.maxResults = maxResultsValue;
|
||||
}
|
||||
|
||||
if (nodeProperties.routing.output.postReceive) {
|
||||
returnData.postReceive.push({
|
||||
data: {
|
||||
parameterValue,
|
||||
},
|
||||
actions: nodeProperties.routing.output.postReceive,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are any child properties
|
||||
if (!Object.prototype.hasOwnProperty.call(nodeProperties, 'options')) {
|
||||
// There are none so nothing else to check
|
||||
return returnData;
|
||||
}
|
||||
|
||||
// Everything after this point can only be of type INodeProperties
|
||||
nodeProperties = nodeProperties as INodeProperties;
|
||||
|
||||
// Check the child parameters
|
||||
let value;
|
||||
if (nodeProperties.type === 'options') {
|
||||
const optionValue = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
nodeProperties.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
||||
// Find the selected option
|
||||
const selectedOption = (nodeProperties.options as INodePropertyOptions[]).filter(
|
||||
(option) => option.value === optionValue,
|
||||
);
|
||||
|
||||
if (selectedOption.length) {
|
||||
// Check only if option is set and if of type INodeProperties
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
executeSingleFunctions,
|
||||
selectedOption[0],
|
||||
itemIndex,
|
||||
runIndex,
|
||||
`${basePath}${nodeProperties.name}`,
|
||||
{ $value: optionValue },
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
}
|
||||
} else if (nodeProperties.type === 'collection') {
|
||||
value = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
nodeProperties.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
||||
for (const propertyOption of nodeProperties.options as INodeProperties[]) {
|
||||
if (
|
||||
Object.keys(value as IDataObject).includes(propertyOption.name) &&
|
||||
propertyOption.type !== undefined
|
||||
) {
|
||||
// Check only if option is set and if of type INodeProperties
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
executeSingleFunctions,
|
||||
propertyOption,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
`${basePath}${nodeProperties.name}`,
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
}
|
||||
}
|
||||
} else if (nodeProperties.type === 'fixedCollection') {
|
||||
basePath = `${basePath}${nodeProperties.name}.`;
|
||||
for (const propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
||||
// Check if the option got set and if not skip it
|
||||
value = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
propertyOptions.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure that it is always an array to be able to use the same code for multi and single
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
|
||||
const loopBasePath = `${basePath}${propertyOptions.name}`;
|
||||
for (let i = 0; i < (value as INodeParameters[]).length; i++) {
|
||||
for (const option of propertyOptions.values) {
|
||||
const tempOptions = this.getRequestOptionsFromParameters(
|
||||
executeSingleFunctions,
|
||||
option,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
nodeProperties.typeOptions?.multipleValues ? `${loopBasePath}[${i}]` : loopBasePath,
|
||||
{ ...(additionalKeys || {}), $index: i, $parent: value[i] },
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
|
@ -12,7 +13,7 @@
|
|||
/* eslint-disable no-continue */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable import/no-cycle */
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
|
||||
import {
|
||||
Expression,
|
||||
IConnections,
|
||||
|
@ -39,6 +40,7 @@ import {
|
|||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
ObservableObject,
|
||||
RoutingNode,
|
||||
WebhookSetupMethodNames,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
|
@ -1078,7 +1080,11 @@ export class Workflow {
|
|||
}
|
||||
|
||||
let connectionInputData: INodeExecutionData[] = [];
|
||||
if (nodeType.execute || nodeType.executeSingle) {
|
||||
if (
|
||||
nodeType.execute ||
|
||||
nodeType.executeSingle ||
|
||||
(!nodeType.poll && !nodeType.trigger && !nodeType.webhook)
|
||||
) {
|
||||
// Only stop if first input is empty for execute & executeSingle runs. For all others run anyways
|
||||
// because then it is a trigger node. As they only pass data through and so the input-data
|
||||
// becomes output-data it has to be possible.
|
||||
|
@ -1217,6 +1223,19 @@ export class Workflow {
|
|||
} else if (nodeType.webhook) {
|
||||
// For webhook nodes always simply pass the data through
|
||||
return inputData.main as INodeExecutionData[][];
|
||||
} else {
|
||||
// For nodes which have routing information on properties
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
this,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
return routingNode.runNode(inputData, runIndex, nodeType, nodeExecuteFunctions);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -8,6 +8,7 @@ export * from './Interfaces';
|
|||
export * from './Expression';
|
||||
export * from './NodeErrors';
|
||||
export * as TelemetryHelpers from './TelemetryHelpers';
|
||||
export * from './RoutingNode';
|
||||
export * from './Workflow';
|
||||
export * from './WorkflowDataProxy';
|
||||
export * from './WorkflowErrors';
|
||||
|
|
|
@ -1,14 +1,526 @@
|
|||
import get from 'lodash.get';
|
||||
import {
|
||||
CredentialInformation,
|
||||
IAdditionalCredentialOptions,
|
||||
IAllExecuteFunctions,
|
||||
IContextObject,
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentials,
|
||||
ICredentialsEncrypted,
|
||||
ICredentialsHelper,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IExecuteResponsePromiseData,
|
||||
IExecuteSingleFunctions,
|
||||
IExecuteWorkflowInfo,
|
||||
IHttpRequestOptions,
|
||||
IN8nHttpFullResponse,
|
||||
IN8nHttpResponse,
|
||||
INode,
|
||||
INodeCredentialsDetails,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
INodeTypes,
|
||||
IRunExecutionData,
|
||||
ITaskDataConnections,
|
||||
IWorkflowBase,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowDataProxyData,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
WorkflowDataProxy,
|
||||
WorkflowExecuteMode,
|
||||
WorkflowHooks,
|
||||
} from '../src';
|
||||
|
||||
export interface INodeTypesObject {
|
||||
[key: string]: INodeType;
|
||||
}
|
||||
|
||||
export class Credentials extends ICredentials {
|
||||
hasNodeAccess(nodeType: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void {
|
||||
this.data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
setDataKey(key: string, data: CredentialInformation, encryptionKey: string): void {
|
||||
let fullData;
|
||||
try {
|
||||
fullData = this.getData(encryptionKey);
|
||||
} catch (e) {
|
||||
fullData = {};
|
||||
}
|
||||
|
||||
fullData[key] = data;
|
||||
|
||||
return this.setData(fullData, encryptionKey);
|
||||
}
|
||||
|
||||
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
|
||||
if (this.data === undefined) {
|
||||
throw new Error('No data is set so nothing can be returned.');
|
||||
}
|
||||
return JSON.parse(this.data);
|
||||
}
|
||||
|
||||
getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation {
|
||||
const fullData = this.getData(encryptionKey, nodeType);
|
||||
|
||||
if (fullData === null) {
|
||||
throw new Error(`No data was set.`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!fullData.hasOwnProperty(key)) {
|
||||
throw new Error(`No data for key "${key}" exists.`);
|
||||
}
|
||||
|
||||
return fullData[key];
|
||||
}
|
||||
|
||||
getDataToSave(): ICredentialsEncrypted {
|
||||
if (this.data === undefined) {
|
||||
throw new Error(`No credentials were set to save.`);
|
||||
}
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
data: this.data,
|
||||
nodesAccess: this.nodesAccess,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CredentialsHelper extends ICredentialsHelper {
|
||||
async authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
requestParams: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
return requestParams;
|
||||
}
|
||||
|
||||
getParentTypes(name: string): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getDecrypted(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
): Promise<ICredentialDataDecryptedObject> {
|
||||
return {};
|
||||
}
|
||||
|
||||
async getCredentials(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
): Promise<ICredentials> {
|
||||
return new Credentials({ id: null, name: '' }, '', [], '');
|
||||
}
|
||||
|
||||
async updateCredentials(
|
||||
nodeCredentials: INodeCredentialsDetails,
|
||||
type: string,
|
||||
data: ICredentialDataDecryptedObject,
|
||||
): Promise<void> {}
|
||||
}
|
||||
|
||||
export function getNodeParameter(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData | null,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
node: INode,
|
||||
parameterName: string,
|
||||
itemIndex: number,
|
||||
mode: WorkflowExecuteMode,
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||
fallbackValue?: any,
|
||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
||||
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||
if (nodeType === undefined) {
|
||||
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
|
||||
}
|
||||
|
||||
const value = get(node.parameters, parameterName, fallbackValue);
|
||||
|
||||
if (value === undefined) {
|
||||
throw new Error(`Could not get parameter "${parameterName}"!`);
|
||||
}
|
||||
|
||||
let returnData;
|
||||
try {
|
||||
returnData = workflow.expression.getParameterValue(
|
||||
value,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys,
|
||||
);
|
||||
} catch (e) {
|
||||
e.message += ` [Error in parameter: "${parameterName}"]`;
|
||||
throw e;
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function getExecuteFunctions(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
inputData: ITaskDataConnections,
|
||||
node: INode,
|
||||
itemIndex: number,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
): IExecuteFunctions {
|
||||
return ((workflow, runExecutionData, connectionInputData, inputData, node) => {
|
||||
return {
|
||||
continueOnFail: () => {
|
||||
return false;
|
||||
},
|
||||
evaluateExpression: (expression: string, itemIndex: number) => {
|
||||
return expression;
|
||||
},
|
||||
async executeWorkflow(
|
||||
workflowInfo: IExecuteWorkflowInfo,
|
||||
inputData?: INodeExecutionData[],
|
||||
): Promise<any> {
|
||||
return additionalData.executeWorkflow(workflowInfo, additionalData, inputData);
|
||||
},
|
||||
getContext(type: string): IContextObject {
|
||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
||||
},
|
||||
async getCredentials(
|
||||
type: string,
|
||||
itemIndex?: number,
|
||||
): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return {
|
||||
apiKey: '12345',
|
||||
};
|
||||
},
|
||||
getExecutionId: (): string => {
|
||||
return additionalData.executionId!;
|
||||
},
|
||||
getInputData: (inputIndex = 0, inputName = 'main') => {
|
||||
if (!inputData.hasOwnProperty(inputName)) {
|
||||
// Return empty array because else it would throw error when nothing is connected to input
|
||||
return [];
|
||||
}
|
||||
|
||||
if (inputData[inputName].length < inputIndex) {
|
||||
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
||||
}
|
||||
|
||||
if (inputData[inputName][inputIndex] === null) {
|
||||
// return [];
|
||||
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
||||
}
|
||||
|
||||
return inputData[inputName][inputIndex] as INodeExecutionData[];
|
||||
},
|
||||
getNodeParameter: (
|
||||
parameterName: string,
|
||||
itemIndex: number,
|
||||
fallbackValue?: any,
|
||||
):
|
||||
| NodeParameterValue
|
||||
| INodeParameters
|
||||
| NodeParameterValue[]
|
||||
| INodeParameters[]
|
||||
| object => {
|
||||
return getNodeParameter(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
node,
|
||||
parameterName,
|
||||
itemIndex,
|
||||
mode,
|
||||
{},
|
||||
fallbackValue,
|
||||
);
|
||||
},
|
||||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
},
|
||||
getNode: () => {
|
||||
return JSON.parse(JSON.stringify(node));
|
||||
},
|
||||
getRestApiUrl: (): string => {
|
||||
return additionalData.restApiUrl;
|
||||
},
|
||||
getTimezone: (): string => {
|
||||
return additionalData.timezone;
|
||||
},
|
||||
getWorkflow: () => {
|
||||
return {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
};
|
||||
},
|
||||
getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => {
|
||||
const dataProxy = new WorkflowDataProxy(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
{},
|
||||
mode,
|
||||
{},
|
||||
);
|
||||
return dataProxy.getDataProxy();
|
||||
},
|
||||
getWorkflowStaticData(type: string): IDataObject {
|
||||
return workflow.getStaticData(type, node);
|
||||
},
|
||||
prepareOutputData: NodeHelpers.prepareOutputData,
|
||||
async putExecutionToWait(waitTill: Date): Promise<void> {
|
||||
runExecutionData.waitTill = waitTill;
|
||||
},
|
||||
sendMessageToUI(...args: any[]): void {
|
||||
if (mode !== 'manual') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (additionalData.sendMessageToUI) {
|
||||
additionalData.sendMessageToUI(node.name, args);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
console.error(`There was a problem sending messsage to UI: ${error.message}`);
|
||||
}
|
||||
},
|
||||
async sendResponse(response: IExecuteResponsePromiseData): Promise<void> {
|
||||
await additionalData.hooks?.executeHookFunctions('sendResponse', [response]);
|
||||
},
|
||||
helpers: {
|
||||
async httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
requestOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
})(workflow, runExecutionData, connectionInputData, inputData, node);
|
||||
}
|
||||
|
||||
export function getExecuteSingleFunctions(
|
||||
workflow: Workflow,
|
||||
runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
inputData: ITaskDataConnections,
|
||||
node: INode,
|
||||
itemIndex: number,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
): IExecuteSingleFunctions {
|
||||
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
||||
return {
|
||||
continueOnFail: () => {
|
||||
return false;
|
||||
},
|
||||
evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => {
|
||||
return expression;
|
||||
},
|
||||
getContext(type: string): IContextObject {
|
||||
return NodeHelpers.getContext(runExecutionData, type, node);
|
||||
},
|
||||
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||
return {
|
||||
apiKey: '12345',
|
||||
};
|
||||
},
|
||||
getInputData: (inputIndex = 0, inputName = 'main') => {
|
||||
if (!inputData.hasOwnProperty(inputName)) {
|
||||
// Return empty array because else it would throw error when nothing is connected to input
|
||||
return { json: {} };
|
||||
}
|
||||
|
||||
if (inputData[inputName].length < inputIndex) {
|
||||
throw new Error(`Could not get input index "${inputIndex}" of input "${inputName}"!`);
|
||||
}
|
||||
|
||||
const allItems = inputData[inputName][inputIndex];
|
||||
|
||||
if (allItems === null) {
|
||||
// return [];
|
||||
throw new Error(`Value "${inputIndex}" of input "${inputName}" did not get set!`);
|
||||
}
|
||||
|
||||
if (allItems[itemIndex] === null) {
|
||||
// return [];
|
||||
throw new Error(
|
||||
`Value "${inputIndex}" of input "${inputName}" with itemIndex "${itemIndex}" did not get set!`,
|
||||
);
|
||||
}
|
||||
|
||||
return allItems[itemIndex];
|
||||
},
|
||||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
},
|
||||
getNode: () => {
|
||||
return JSON.parse(JSON.stringify(node));
|
||||
},
|
||||
getRestApiUrl: (): string => {
|
||||
return additionalData.restApiUrl;
|
||||
},
|
||||
getTimezone: (): string => {
|
||||
return additionalData.timezone;
|
||||
},
|
||||
getNodeParameter: (
|
||||
parameterName: string,
|
||||
fallbackValue?: any,
|
||||
):
|
||||
| NodeParameterValue
|
||||
| INodeParameters
|
||||
| NodeParameterValue[]
|
||||
| INodeParameters[]
|
||||
| object => {
|
||||
return getNodeParameter(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
node,
|
||||
parameterName,
|
||||
itemIndex,
|
||||
mode,
|
||||
{},
|
||||
fallbackValue,
|
||||
);
|
||||
},
|
||||
getWorkflow: () => {
|
||||
return {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
};
|
||||
},
|
||||
getWorkflowDataProxy: (): IWorkflowDataProxyData => {
|
||||
const dataProxy = new WorkflowDataProxy(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
{},
|
||||
mode,
|
||||
{},
|
||||
);
|
||||
return dataProxy.getDataProxy();
|
||||
},
|
||||
getWorkflowStaticData(type: string): IDataObject {
|
||||
return workflow.getStaticData(type, node);
|
||||
},
|
||||
helpers: {
|
||||
async httpRequest(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IN8nHttpFullResponse | IN8nHttpResponse> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
requestOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async requestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
async httpRequestWithAuthentication(
|
||||
this: IAllExecuteFunctions,
|
||||
credentialsType: string,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
additionalCredentialOptions?: IAdditionalCredentialOptions,
|
||||
): Promise<any> {
|
||||
return {
|
||||
body: {
|
||||
headers: {},
|
||||
statusCode: 200,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
additionalCredentialOptions,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
})(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex);
|
||||
}
|
||||
|
||||
class NodeTypesClass implements INodeTypes {
|
||||
nodeTypes: INodeTypeData = {
|
||||
'test.set': {
|
||||
|
@ -120,3 +632,27 @@ export function NodeTypes(): NodeTypesClass {
|
|||
|
||||
return nodeTypesInstance;
|
||||
}
|
||||
|
||||
export function WorkflowExecuteAdditionalData(): IWorkflowExecuteAdditionalData {
|
||||
const workflowData: IWorkflowBase = {
|
||||
name: '',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
active: true,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
};
|
||||
|
||||
return {
|
||||
credentialsHelper: new CredentialsHelper(''),
|
||||
hooks: new WorkflowHooks({}, 'trigger', '1', workflowData),
|
||||
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {},
|
||||
sendMessageToUI: (message: string) => {},
|
||||
restApiUrl: '',
|
||||
encryptionKey: 'test',
|
||||
timezone: 'America/New_York',
|
||||
webhookBaseUrl: 'webhook',
|
||||
webhookWaitingBaseUrl: 'webhook-waiting',
|
||||
webhookTestBaseUrl: 'webhook-test',
|
||||
};
|
||||
}
|
||||
|
|
1680
packages/workflow/test/RoutingNode.test.ts
Normal file
1680
packages/workflow/test/RoutingNode.test.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2017"
|
||||
"es2019"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
|
@ -11,13 +11,14 @@
|
|||
"module": "commonjs",
|
||||
"removeComments": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist/",
|
||||
"target": "es2017",
|
||||
"target": "es2019",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
|
|
Loading…
Reference in a new issue