From 336fc9e2a820476931a9e9b482e4be284c0337d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 24 May 2022 11:36:19 +0200 Subject: [PATCH] feat(core): Allow credential reuse on HTTP Request node (#3228) * :sparkles: Create controller * :zap: Mount controller * :pencil2: Add error messages * :sparkles: Create scopes fetcher * :zap: Account for non-existent credential type * :blue_book: Type scopes request * :zap: Adjust error message * :test_tube: Add tests * :sparkles: Introduce simple node versioning * :zap: Add example how to read version in node-code for custom logic * :bug: Fix setting of parameters * :bug: Fix another instance where it sets the wrong parameter * :zap: Remove unnecessary TOODs * :sparkles: Re-version HTTP Request node * :shirt: Satisfy linter * :zap: Retrieve node version * :rewind: Undo Jan's changes to Set node * :test_tube: Fix CI/CD for `/oauth2-credential` tests (#3230) * :bug: Fix notice warning missing background color (#3231) * :bug: Check for generic auth in node cred types * :zap: Refactor credentials dropdown for HTTP Request node (#3222) * :zap: Discoverability flow (#3229) * :sparkles: Added node credentials type proxy. Changed node credentials input order. * :zap: Add computed property from versioning branch * :bug: Fix cred ref lost and unsaved * :zap: Make options consistent with cred type names * :zap: Use prop to set component order * :zap: Use constant and version * :zap: Fix rendering for generic auth creds * :zap: Mark as required on first selection * :zap: Implement discoverability flow * :zap: Mark as required on subsequent selections * :zap: Fix marking as required after cred deletion * :zap: Refactor to clean up * :zap: Detect position automatically * :zap: Add i18n to option label * :zap: Hide subtitle for custom action * :zap: Detect active credential type * :zap: Prop drilling to re-render select * :fire: Remove unneeded property * :pencil2: Rename arg * :fire: Remove unused import * :fire: Remove unneeded getters * :fire: Remove unused import * :zap: Generalize cred component positioning * :zap: Set up request * :bug: Fix edge case in endpoint * :zap: Display scopes alert box * :rewind: Revert "Generalize cred comp positioning" This reverts commit 75eea89273b854110fa6d1f96c7c1d78dd3b0731. * :zap: Consolidate HTTPRN check * :zap: Fix hue percentage to degree * :fire: Remove unused import * :fire: Remove unused import * :fire: Remove unused class * :fire: Remove unused import * :blue_book: Create type for HTTPRN v2 auth params * :pencil2: Rename check * :fire: Remove unused import * :pencil2: Add i18n to `reportUnsetCredential()` * :zap: Refactor Alex's spacing changes * :zap: Post-merge fixes * :zap: Add docs link * :fire: Exclude Notion OAuth cred * :pencil2: Update copy * :pencil2: Rename param * :art: Reposition notice and simplify styling * :pencil2: Update copy * :pencil2: Update copy * :zap: Hide params during custom action * :zap: Show notice if any cred type supported * :bug: Prevent scopes text overflow * :fire: Remove superfluous check * :pencil2: Break up docstring * :art: Tweak notice styling * :zap: Reorder cred param in Webhook node * :pencil2: Shorten cred name in scopes notice * :test_tube: Update Notice snapshots * :bug: Fix check when `globalRole` is `undefined` * :rewind: Revert 3f2c4a6 * :zap: Apply feedback from Product * :test_tube: Update snapshot * :zap: Adjust regex expansion pattern for singular * :fire: Remove unused import * :fire: Remove logging * :zap: Make `somethingElse` key more unique * :zap: Move something else to constants * :zap: Consolidate notice component * :zap: Apply latest feedback * :test_tube: Update tests * :test_tube: Update snapshot * :pencil2: Fix singular version * :test_tube: Finalize tests * :pencil2: Rename constant * :test_tube: Expand tests * :fire: Remove `truncate` prop * :truck: Move scopes fetching to store * :truck: Move method to component * :zap: Use constant * :zap: Refactor `Notice` component * :test_tube: Update tests * :fire: Remove unused keys * :zap: Inject custom API call option * :fire: Remove unused props * :art: Use `compact` prop * :test_tube: Update snapshots * :truck: Move scopes to store * :truck: Move `nodeCredentialTypes` to parent * :pencil2: Rename cred types per branding * :bug: Clear scopes when none * :zap: Add default * :truck: Move `newHttpRequestNodeCredentialType` to parent * :fire: Remove test data * :zap: Separate lines for readability * :zap: Change reference from node to node name * :pencil2: Rename i18n keys * :zap: Refactor OAuth check * :fire: Remove unused key * :truck: Move `OAuth1/2 API` to i18n * :zap: Refactor `skipCheck` * :zap: Add `stopPropagation` and `preventDefault` * :truck: Move active credential scopes logic to store * :art: Fix spacing for `NodeWebhooks` component * :zap: Implement feedback * :zap: Update HTTPRN default and issue copy * Refactor to use `CredentialsSelect` param (#3304) * :zap: Refactor into cred type param * :zap: Componentize scopes notice * :fire: Remove unused data * :fire: Remove unused `loadOptions` * :zap: Componentize `NodeCredentialType` * :bug: Fix param validation * :fire: Remove dup methods * :zap: Refactor all references to `isHttpRequestNodeV2` * :art: Fix styling * :fire: Remove unused import * :fire: Remove unused properties * :art: Fix spacing for Pipedrive Trigger node * :art: Undo Webhook node styling change * :fire: Remove unused style * :zap: Cover `httpHeaderAuth` edge case * :bug: Fix `this.node` reference * :truck: Rename to `credentialsSelect` * :bug: Fix mistaken renaming * :zap: Set one attribute per line * :zap: Move condition to instantiation site * :truck: Rename prop * :zap: Refactor away `prepareScopesNotice` * :pencil2: Rename i18n keys * :pencil2: Update i18n calls * :pencil2: Add more i18n keys * :fire: Remove unused props * :pencil2: Add explanatory comment * :zap: Adjust check in `hasProxyAuth` * :zap: Refactor `credentialSelected` from prop to event * :zap: Eventify `valueChanged`, `setFocus`, `onBlur` * :zap: Eventify `optionSelected` * :zap: Add `noDataExpression` * :fire: Remove logging * :fire: Remove URL from scopes * :zap: Disregard expressions for display * :art: Use CSS modules * :blue_book: Tigthen interface * :bug: Fix generic auth display * :bug: Fix generic auth validation * :blue_book: Loosen type * :truck: Move event params to end * :zap: Generalize reference * :zap: Refactor generic auth as `credentialsSelect` param * :rewind: Restore check for `httpHeaderAuth ` * :truck: Rename `existing` to `predefined` * Extend metrics for HTTP Request node (#3282) * :zap: Extend metrics * :test_tube: Add tests * :zap: Update param names Co-authored-by: Alex Grozav * :zap: Update check per new branch * :zap: Include generic auth check * :zap: Adjust telemetry (#3359) * :zap: Filter credential types by label Co-authored-by: Jan Oberhauser Co-authored-by: Alex Grozav --- packages/cli/src/CredentialTypes.ts | 7 +- packages/cli/src/Server.ts | 59 +++- packages/cli/src/requests.d.ts | 4 +- .../src/components/N8nNotice/Notice.vue | 76 +++-- .../N8nNotice/__tests__/Notice.spec.ts | 47 +-- .../__snapshots__/Notice.spec.ts.snap | 37 ++- packages/design-system/theme/src/_tokens.scss | 2 +- packages/editor-ui/src/Interface.ts | 3 + .../src/components/CredentialsSelect.vue | 153 +++++++++ packages/editor-ui/src/components/Node.vue | 8 +- .../src/components/NodeCredentials.vue | 39 ++- .../editor-ui/src/components/NodeSettings.vue | 29 +- .../src/components/ParameterInput.vue | 162 +++++++--- .../src/components/ParameterInputList.vue | 32 +- .../src/components/ParameterIssues.vue | 30 ++ .../src/components/ParameterOptions.vue | 68 ++++ .../editor-ui/src/components/ScopesNotice.vue | 55 ++++ .../src/components/mixins/nodeHelpers.ts | 116 +++++++ .../src/components/mixins/showMessage.ts | 27 +- .../src/components/mixins/workflowHelpers.ts | 5 + packages/editor-ui/src/constants.ts | 3 + packages/editor-ui/src/modules/credentials.ts | 30 ++ .../editor-ui/src/plugins/i18n/docs/README.md | 14 +- .../src/plugins/i18n/locales/en.json | 9 + .../credentials/GithubApi.credentials.ts | 2 +- .../GithubOAuth2Api.credentials.ts | 2 +- .../credentials/GitlabApi.credentials.ts | 2 +- .../GitlabOAuth2Api.credentials.ts | 2 +- .../credentials/HttpBasicAuth.credentials.ts | 1 + .../credentials/HttpDigestAuth.credentials.ts | 1 + .../credentials/HttpHeaderAuth.credentials.ts | 1 + .../credentials/HttpQueryAuth.credentials.ts | 1 + .../credentials/HubspotApi.credentials.ts | 2 +- .../HubspotAppToken.credentials.ts | 2 +- .../HubspotDeveloperApi.credentials.ts | 2 +- .../HubspotOAuth2Api.credentials.ts | 2 +- .../credentials/OAuth1Api.credentials.ts | 1 + .../credentials/OAuth2Api.credentials.ts | 1 + .../nodes/HttpRequest/HttpRequest.node.ts | 302 +++++++++++++++--- packages/nodes-base/package.json | 1 - packages/workflow/src/Interfaces.ts | 16 +- packages/workflow/src/TelemetryHelpers.ts | 77 ++++- .../workflow/test/TelemetryHelpers.test.ts | 191 +++++++++++ 43 files changed, 1396 insertions(+), 228 deletions(-) create mode 100644 packages/editor-ui/src/components/CredentialsSelect.vue create mode 100644 packages/editor-ui/src/components/ParameterIssues.vue create mode 100644 packages/editor-ui/src/components/ParameterOptions.vue create mode 100644 packages/editor-ui/src/components/ScopesNotice.vue create mode 100644 packages/workflow/test/TelemetryHelpers.test.ts diff --git a/packages/cli/src/CredentialTypes.ts b/packages/cli/src/CredentialTypes.ts index 7f48050038..eceeb35fed 100644 --- a/packages/cli/src/CredentialTypes.ts +++ b/packages/cli/src/CredentialTypes.ts @@ -3,6 +3,7 @@ import { ICredentialTypeData, ICredentialTypes as ICredentialTypesInterface, } from 'n8n-workflow'; +import { RESPONSE_ERROR_MESSAGES } from './constants'; class CredentialTypesClass implements ICredentialTypesInterface { credentialTypes: ICredentialTypeData = {}; @@ -16,7 +17,11 @@ class CredentialTypesClass implements ICredentialTypesInterface { } getByName(credentialType: string): ICredentialType { - return this.credentialTypes[credentialType].type; + try { + return this.credentialTypes[credentialType].type; + } catch (error) { + throw new Error(`${RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL}: ${credentialType}`); + } } } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index ee99f24715..e478642809 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1456,7 +1456,7 @@ class App { if (defaultLocale === 'en') { return nodeInfos.reduce((acc, { name, version }) => { const { description } = NodeTypes().getByNameAndVersion(name, version); - acc.push(description); + acc.push(injectCustomApiCallOption(description)); return acc; }, []); } @@ -1480,7 +1480,7 @@ class App { // ignore - no translation exists at path } - nodeTypes.push(description); + nodeTypes.push(injectCustomApiCallOption(description)); } const nodeTypes: INodeTypeDescription[] = []; @@ -3114,3 +3114,58 @@ async function getExecutionsCount( return { count, estimated: false }; } + +const CUSTOM_API_CALL_NAME = 'Custom API Call'; +const CUSTOM_API_CALL_KEY = '__CUSTOM_API_CALL__'; + +/** + * Inject a `Custom API Call` option into `resource` and `operation` + * parameters in a node that supports proxy auth. + */ +function injectCustomApiCallOption(description: INodeTypeDescription) { + if (!supportsProxyAuth(description)) return description; + + description.properties.forEach((p) => { + if ( + ['resource', 'operation'].includes(p.name) && + Array.isArray(p.options) && + p.options[p.options.length - 1].name !== CUSTOM_API_CALL_NAME + ) { + p.options.push({ + name: CUSTOM_API_CALL_NAME, + value: CUSTOM_API_CALL_KEY, + }); + } + + return p; + }); + + return description; +} + +const credentialTypes = CredentialTypes(); + +/** + * Whether any of the node's credential types may be used to + * make a request from a node other than itself. + */ +function supportsProxyAuth(description: INodeTypeDescription) { + if (!description.credentials) return false; + + return description.credentials.some(({ name }) => { + const credType = credentialTypes.getByName(name); + + if (credType.authenticate !== undefined) return true; + + return isOAuth(credType); + }); +} + +function isOAuth(credType: ICredentialType) { + return ( + Array.isArray(credType.extends) && + credType.extends.some((parentType) => + ['oAuth2Api', 'googleOAuth2Api', 'oAuth1Api'].includes(parentType), + ) + ); +} diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index 276c06346f..e373cb2ad0 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -244,9 +244,7 @@ export declare namespace OAuthRequest { namespace OAuth2Credential { type Auth = OAuth1Credential.Auth; - type Callback = AuthenticatedRequest<{}, {}, {}, { code: string; state: string }> & { - user?: User; - }; + type Callback = AuthenticatedRequest<{}, {}, {}, { code: string; state: string }>; } } diff --git a/packages/design-system/src/components/N8nNotice/Notice.vue b/packages/design-system/src/components/N8nNotice/Notice.vue index bdd7bcec24..55571d3ec5 100644 --- a/packages/design-system/src/components/N8nNotice/Notice.vue +++ b/packages/design-system/src/components/N8nNotice/Notice.vue @@ -1,24 +1,14 @@