feat(core): Allow credential reuse on HTTP Request node (#3228)

*  Create controller

*  Mount controller

* ✏️ Add error messages

*  Create scopes fetcher

*  Account for non-existent credential type

* 📘 Type scopes request

*  Adjust error message

* 🧪 Add tests

*  Introduce simple node versioning

*  Add example how to read version in node-code for custom logic

* 🐛 Fix setting of parameters

* 🐛 Fix another instance where it sets the wrong parameter

*  Remove unnecessary TOODs

*  Re-version HTTP Request node

* 👕 Satisfy linter

*  Retrieve node version

*  Undo Jan's changes to Set node

* 🧪 Fix CI/CD for `/oauth2-credential` tests (#3230)

* 🐛 Fix notice warning missing background color (#3231)

* 🐛 Check for generic auth in node cred types

*  Refactor credentials dropdown for HTTP Request node (#3222)

*  Discoverability flow (#3229)

*  Added node credentials type proxy. Changed node credentials input order.

*  Add computed property from versioning branch

* 🐛 Fix cred ref lost and unsaved

*  Make options consistent with cred type names

*  Use prop to set component order

*  Use constant and version

*  Fix rendering for generic auth creds

*  Mark as required on first selection

*  Implement discoverability flow

*  Mark as required on subsequent selections

*  Fix marking as required after cred deletion

*  Refactor to clean up

*  Detect position automatically

*  Add i18n to option label

*  Hide subtitle for custom action

*  Detect active credential type

*  Prop drilling to re-render select

* 🔥 Remove unneeded property

* ✏️ Rename arg

* 🔥 Remove unused import

* 🔥 Remove unneeded getters

* 🔥 Remove unused import

*  Generalize cred component positioning

*  Set up request

* 🐛 Fix edge case in endpoint

*  Display scopes alert box

*  Revert "Generalize cred comp positioning"

This reverts commit 75eea89273b854110fa6d1f96c7c1d78dd3b0731.

*  Consolidate HTTPRN check

*  Fix hue percentage to degree

* 🔥 Remove unused import

* 🔥 Remove unused import

* 🔥 Remove unused class

* 🔥 Remove unused import

* 📘 Create type for HTTPRN v2 auth params

* ✏️ Rename check

* 🔥 Remove unused import

* ✏️ Add i18n to `reportUnsetCredential()`

*  Refactor Alex's spacing changes

*  Post-merge fixes

*  Add docs link

* 🔥 Exclude Notion OAuth cred

* ✏️ Update copy

* ✏️ Rename param

* 🎨 Reposition notice and simplify styling

* ✏️ Update copy

* ✏️ Update copy

*  Hide params during custom action

*  Show notice if any cred type supported

* 🐛 Prevent scopes text overflow

* 🔥 Remove superfluous check

* ✏️ Break up docstring

* 🎨 Tweak notice styling

*  Reorder cred param in Webhook node

* ✏️ Shorten cred name in scopes notice

* 🧪 Update Notice snapshots

* 🐛 Fix check when `globalRole` is `undefined`

*  Revert 3f2c4a6

*  Apply feedback from Product

* 🧪 Update snapshot

*  Adjust regex expansion pattern for singular

* 🔥 Remove unused import

* 🔥 Remove logging

*  Make `somethingElse` key more unique

*  Move something else to constants

*  Consolidate notice component

*  Apply latest feedback

* 🧪 Update tests

* 🧪 Update snapshot

* ✏️ Fix singular version

* 🧪 Finalize tests

* ✏️ Rename constant

* 🧪 Expand tests

* 🔥 Remove `truncate` prop

* 🚚 Move scopes fetching to store

* 🚚 Move method to component

*  Use constant

*  Refactor `Notice` component

* 🧪 Update tests

* 🔥 Remove unused keys

*  Inject custom API call option

* 🔥 Remove unused props

* 🎨 Use `compact` prop

* 🧪 Update snapshots

* 🚚 Move scopes to store

* 🚚 Move `nodeCredentialTypes` to parent

* ✏️ Rename cred types per branding

* 🐛 Clear scopes when none

*  Add default

* 🚚 Move `newHttpRequestNodeCredentialType` to parent

* 🔥 Remove test data

*  Separate lines for readability

*  Change reference from node to node name

* ✏️ Rename i18n keys

*  Refactor OAuth check

* 🔥 Remove unused key

* 🚚 Move `OAuth1/2 API` to i18n

*  Refactor `skipCheck`

*  Add `stopPropagation` and `preventDefault`

* 🚚 Move active credential scopes logic to store

* 🎨 Fix spacing for `NodeWebhooks` component

*  Implement feedback

*  Update HTTPRN default and issue copy

* Refactor to use `CredentialsSelect` param (#3304)

*  Refactor into cred type param

*  Componentize scopes notice

* 🔥 Remove unused data

* 🔥 Remove unused `loadOptions`

*  Componentize `NodeCredentialType`

* 🐛 Fix param validation

* 🔥 Remove dup methods

*  Refactor all references to `isHttpRequestNodeV2`

* 🎨 Fix styling

* 🔥 Remove unused import

* 🔥 Remove unused properties

* 🎨 Fix spacing for Pipedrive Trigger node

* 🎨 Undo Webhook node styling change

* 🔥 Remove unused style

*  Cover `httpHeaderAuth` edge case

* 🐛 Fix `this.node` reference

* 🚚 Rename to `credentialsSelect`

* 🐛 Fix mistaken renaming

*  Set one attribute per line

*  Move condition to instantiation site

* 🚚 Rename prop

*  Refactor away `prepareScopesNotice`

* ✏️ Rename i18n keys

* ✏️ Update i18n calls

* ✏️ Add more i18n keys

* 🔥 Remove unused props

* ✏️ Add explanatory comment

*  Adjust check in `hasProxyAuth`

*  Refactor `credentialSelected` from prop to event

*  Eventify `valueChanged`, `setFocus`, `onBlur`

*  Eventify `optionSelected`

*  Add `noDataExpression`

* 🔥 Remove logging

* 🔥 Remove URL from scopes

*  Disregard expressions for display

* 🎨 Use CSS modules

* 📘 Tigthen interface

* 🐛 Fix generic auth display

* 🐛 Fix generic auth validation

* 📘 Loosen type

* 🚚 Move event params to end

*  Generalize reference

*  Refactor generic auth as `credentialsSelect` param

*  Restore check for `httpHeaderAuth `

* 🚚 Rename `existing` to `predefined`

* Extend metrics for HTTP Request node (#3282)

*  Extend metrics

* 🧪 Add tests

*  Update param names

Co-authored-by: Alex Grozav <alex@grozav.com>

*  Update check per new branch

*  Include generic auth check

*  Adjust telemetry (#3359)

*  Filter credential types by label

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
Co-authored-by: Alex Grozav <alex@grozav.com>
This commit is contained in:
Iván Ovejero
2022-05-24 11:36:19 +02:00
committed by GitHub
parent 0212d65dae
commit 336fc9e2a8
43 changed files with 1396 additions and 228 deletions

View File

@@ -1,10 +1,14 @@
import path from 'path';
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IAuthenticate,
IBinaryData,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
NodeApiError,
@@ -29,7 +33,7 @@ export class HttpRequest implements INodeType {
name: 'httpRequest',
icon: 'fa:at',
group: ['input'],
version: 1,
version: [1, 2],
subtitle: '={{$parameter["requestMethod"] + ": " + $parameter["url"]}}',
description: 'Makes an HTTP request and returns the response data',
defaults: {
@@ -39,6 +43,97 @@ export class HttpRequest implements INodeType {
inputs: ['main'],
outputs: ['main'],
credentials: [
// ----------------------------------
// v2 creds
// ----------------------------------
{
name: 'httpBasicAuth',
required: true,
displayOptions: {
show: {
authentication: [
'httpBasicAuth',
],
'@version': [
2,
],
},
},
},
{
name: 'httpDigestAuth',
required: true,
displayOptions: {
show: {
authentication: [
'httpDigestAuth',
],
'@version': [
2,
],
},
},
},
{
name: 'httpHeaderAuth',
required: true,
displayOptions: {
show: {
authentication: [
'httpHeaderAuth',
],
'@version': [
2,
],
},
},
},
{
name: 'httpQueryAuth',
required: true,
displayOptions: {
show: {
authentication: [
'httpQueryAuth',
],
'@version': [
2,
],
},
},
},
{
name: 'oAuth1Api',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth1Api',
],
'@version': [
2,
],
},
},
},
{
name: 'oAuth2Api',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2Api',
],
'@version': [
2,
],
},
},
},
// ----------------------------------
// v1 creds
// ----------------------------------
{
name: 'httpBasicAuth',
required: true,
@@ -47,6 +142,9 @@ export class HttpRequest implements INodeType {
authentication: [
'basicAuth',
],
'@version': [
1,
],
},
},
},
@@ -58,6 +156,9 @@ export class HttpRequest implements INodeType {
authentication: [
'digestAuth',
],
'@version': [
1,
],
},
},
},
@@ -69,6 +170,9 @@ export class HttpRequest implements INodeType {
authentication: [
'headerAuth',
],
'@version': [
1,
],
},
},
},
@@ -80,6 +184,9 @@ export class HttpRequest implements INodeType {
authentication: [
'queryAuth',
],
'@version': [
1,
],
},
},
},
@@ -91,6 +198,9 @@ export class HttpRequest implements INodeType {
authentication: [
'oAuth1',
],
'@version': [
1,
],
},
},
},
@@ -107,6 +217,87 @@ export class HttpRequest implements INodeType {
},
],
properties: [
// ----------------------------------
// v2 params
// ----------------------------------
{
displayName: 'Authentication',
name: 'authentication',
noDataExpression: true,
type: 'options',
required: true,
options: [
{
name: 'None',
value: 'none',
},
{
name: 'Predefined Credential Type',
value: 'predefinedCredentialType',
description: 'We\'ve already implemented auth for many services so that you don\'t have to set it up manually',
},
{
name: 'Generic Credential Type',
value: 'genericCredentialType',
description: 'Fully customizable. Choose between basic, header, OAuth2, etc.',
},
],
default: 'none',
displayOptions: {
show: {
'@version': [
2,
],
},
},
},
{
displayName: 'Credential Type',
name: 'nodeCredentialType',
type: 'credentialsSelect',
noDataExpression: true,
required: true,
default: '',
credentialTypes: [
'extends:oAuth2Api',
'extends:oAuth1Api',
'has:authenticate',
],
displayOptions: {
show: {
authentication: [
'predefinedCredentialType',
],
'@version': [
2,
],
},
},
},
{
displayName: 'Generic Auth Type',
name: 'genericAuthType',
type: 'credentialsSelect',
required: true,
default: '',
credentialTypes: [
'has:genericAuth',
],
displayOptions: {
show: {
authentication: [
'genericCredentialType',
],
'@version': [
2,
],
},
},
},
// ----------------------------------
// v1 params
// ----------------------------------
{
displayName: 'Authentication',
name: 'authentication',
@@ -143,7 +334,18 @@ export class HttpRequest implements INodeType {
],
default: 'none',
description: 'The way to authenticate',
displayOptions: {
show: {
'@version': [
1,
],
},
},
},
// ----------------------------------
// versionless params
// ----------------------------------
{
displayName: 'Request Method',
name: 'requestMethod',
@@ -642,7 +844,6 @@ export class HttpRequest implements INodeType {
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
@@ -653,47 +854,46 @@ export class HttpRequest implements INodeType {
'statusMessage',
];
// TODO: Should have a setting which makes clear that this parameter can not change for each item
const requestMethod = this.getNodeParameter('requestMethod', 0) as string;
const parametersAreJson = this.getNodeParameter('jsonParameters', 0) as boolean;
let authentication;
const nodeVersion = this.getNode().typeVersion;
const responseFormat = this.getNodeParameter('responseFormat', 0) as string;
try {
authentication = this.getNodeParameter('authentication', 0) as 'predefinedCredentialType' | 'genericCredentialType' | 'none';
} catch (_) {}
let httpBasicAuth;
let httpDigestAuth;
let httpHeaderAuth;
let httpQueryAuth;
let oAuth1Api;
let oAuth2Api;
let nodeCredentialType;
try {
httpBasicAuth = await this.getCredentials('httpBasicAuth');
} catch (error) {
// Do nothing
}
try {
httpDigestAuth = await this.getCredentials('httpDigestAuth');
} catch (error) {
// Do nothing
}
try {
httpHeaderAuth = await this.getCredentials('httpHeaderAuth');
} catch (error) {
// Do nothing
}
try {
httpQueryAuth = await this.getCredentials('httpQueryAuth');
} catch (error) {
// Do nothing
}
try {
oAuth1Api = await this.getCredentials('oAuth1Api');
} catch (error) {
// Do nothing
}
try {
oAuth2Api = await this.getCredentials('oAuth2Api');
} catch (error) {
// Do nothing
if (authentication === 'genericCredentialType' || nodeVersion === 1) {
try {
httpBasicAuth = await this.getCredentials('httpBasicAuth');
} catch (_) {}
try {
httpDigestAuth = await this.getCredentials('httpDigestAuth');
} catch (_) {}
try {
httpHeaderAuth = await this.getCredentials('httpHeaderAuth');
} catch (_) {}
try {
httpQueryAuth = await this.getCredentials('httpQueryAuth');
} catch (_) {}
try {
oAuth1Api = await this.getCredentials('oAuth1Api');
} catch (_) {}
try {
oAuth2Api = await this.getCredentials('oAuth2Api');
} catch (_) {}
} else if (authentication === 'predefinedCredentialType') {
try {
nodeCredentialType = this.getNodeParameter('nodeCredentialType', 0) as string;
} catch (_) {}
}
let requestOptions: OptionsWithUri;
@@ -723,6 +923,9 @@ export class HttpRequest implements INodeType {
const returnItems: INodeExecutionData[] = [];
const requestPromises = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const requestMethod = this.getNodeParameter('requestMethod', itemIndex) as string;
const parametersAreJson = this.getNodeParameter('jsonParameters', itemIndex) as boolean;
const options = this.getNodeParameter('options', itemIndex, {}) as IDataObject;
const url = this.getNodeParameter('url', itemIndex) as string;
@@ -983,13 +1186,30 @@ export class HttpRequest implements INodeType {
this.sendMessageToUI(sendRequest);
} catch (e) { }
// Now that the options are all set make the actual http request
if (oAuth1Api !== undefined) {
requestPromises.push(this.helpers.requestOAuth1.call(this, 'oAuth1Api', requestOptions));
} else if (oAuth2Api !== undefined) {
requestPromises.push(this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions, { tokenType: 'Bearer' }));
} else {
requestPromises.push(this.helpers.request(requestOptions));
if (
authentication === 'genericCredentialType' ||
authentication === 'none' ||
nodeVersion === 1
) {
if (oAuth1Api) {
requestPromises.push(
this.helpers.requestOAuth1.call(this, 'oAuth1Api', requestOptions),
);
} else if (oAuth2Api) {
requestPromises.push(
this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions, { tokenType: 'Bearer' }),
);
} else {
// bearerAuth, queryAuth, headerAuth, digestAuth, none
requestPromises.push(
this.helpers.request(requestOptions),
);
}
} else if (authentication === 'predefinedCredentialType' && nodeCredentialType) {
// service-specific cred: OAuth1, OAuth2, plain
requestPromises.push(
this.helpers.requestWithAuthentication.call(this, nodeCredentialType, requestOptions),
);
}
}