mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(HTTP Request Node): Add support for Bearer Auth in HttpRequest node (#15043)
This commit is contained in:
@@ -151,6 +151,7 @@ module.exports = {
|
|||||||
files: ['**/*.test.ts', '**/test/**/*.ts'],
|
files: ['**/*.test.ts', '**/test/**/*.ts'],
|
||||||
rules: {
|
rules: {
|
||||||
'import/no-extraneous-dependencies': 'off',
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
'n8n-nodes-base/node-filename-against-convention': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ module.exports = {
|
|||||||
files: ['**/*.test.ts', '**/test/**/*.ts'],
|
files: ['**/*.test.ts', '**/test/**/*.ts'],
|
||||||
rules: {
|
rules: {
|
||||||
'import/no-extraneous-dependencies': 'off',
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
'n8n-nodes-base/node-filename-against-convention': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import { type INodeTypeBaseDescription, type ITriggerFunctions } from 'n8n-workflow';
|
import { type INodeTypeBaseDescription, type ITriggerFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
|
||||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||||
import nock from 'nock';
|
import nock from 'nock';
|
||||||
|
|
||||||
|
|||||||
@@ -640,6 +640,7 @@ export class HttpRequestV2 implements INodeType {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
let httpBasicAuth;
|
let httpBasicAuth;
|
||||||
|
let httpBearerAuth;
|
||||||
let httpDigestAuth;
|
let httpDigestAuth;
|
||||||
let httpHeaderAuth;
|
let httpHeaderAuth;
|
||||||
let httpQueryAuth;
|
let httpQueryAuth;
|
||||||
@@ -654,6 +655,10 @@ export class HttpRequestV2 implements INodeType {
|
|||||||
try {
|
try {
|
||||||
httpBasicAuth = await this.getCredentials('httpBasicAuth');
|
httpBasicAuth = await this.getCredentials('httpBasicAuth');
|
||||||
} catch {}
|
} catch {}
|
||||||
|
} else if (genericAuthType === 'httpBearerAuth') {
|
||||||
|
try {
|
||||||
|
httpBearerAuth = await this.getCredentials('httpBearerAuth');
|
||||||
|
} catch {}
|
||||||
} else if (genericAuthType === 'httpDigestAuth') {
|
} else if (genericAuthType === 'httpDigestAuth') {
|
||||||
try {
|
try {
|
||||||
httpDigestAuth = await this.getCredentials('httpDigestAuth');
|
httpDigestAuth = await this.getCredentials('httpDigestAuth');
|
||||||
@@ -959,6 +964,11 @@ export class HttpRequestV2 implements INodeType {
|
|||||||
};
|
};
|
||||||
authDataKeys.auth = ['pass'];
|
authDataKeys.auth = ['pass'];
|
||||||
}
|
}
|
||||||
|
if (httpBearerAuth !== undefined) {
|
||||||
|
requestOptions.headers = requestOptions.headers ?? {};
|
||||||
|
requestOptions.headers.Authorization = `Bearer ${String(httpBearerAuth.token)}`;
|
||||||
|
authDataKeys.headers = ['Authorization'];
|
||||||
|
}
|
||||||
if (httpHeaderAuth !== undefined) {
|
if (httpHeaderAuth !== undefined) {
|
||||||
requestOptions.headers![httpHeaderAuth.name as string] = httpHeaderAuth.value;
|
requestOptions.headers![httpHeaderAuth.name as string] = httpHeaderAuth.value;
|
||||||
authDataKeys.headers = [httpHeaderAuth.name as string];
|
authDataKeys.headers = [httpHeaderAuth.name as string];
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export class HttpRequestV3 implements INodeType {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
let httpBasicAuth;
|
let httpBasicAuth;
|
||||||
|
let httpBearerAuth;
|
||||||
let httpDigestAuth;
|
let httpDigestAuth;
|
||||||
let httpHeaderAuth;
|
let httpHeaderAuth;
|
||||||
let httpQueryAuth;
|
let httpQueryAuth;
|
||||||
@@ -156,6 +157,8 @@ export class HttpRequestV3 implements INodeType {
|
|||||||
|
|
||||||
if (genericCredentialType === 'httpBasicAuth') {
|
if (genericCredentialType === 'httpBasicAuth') {
|
||||||
httpBasicAuth = await this.getCredentials('httpBasicAuth', itemIndex);
|
httpBasicAuth = await this.getCredentials('httpBasicAuth', itemIndex);
|
||||||
|
} else if (genericCredentialType === 'httpBearerAuth') {
|
||||||
|
httpBearerAuth = await this.getCredentials('httpBearerAuth', itemIndex);
|
||||||
} else if (genericCredentialType === 'httpDigestAuth') {
|
} else if (genericCredentialType === 'httpDigestAuth') {
|
||||||
httpDigestAuth = await this.getCredentials('httpDigestAuth', itemIndex);
|
httpDigestAuth = await this.getCredentials('httpDigestAuth', itemIndex);
|
||||||
} else if (genericCredentialType === 'httpHeaderAuth') {
|
} else if (genericCredentialType === 'httpHeaderAuth') {
|
||||||
@@ -496,6 +499,11 @@ export class HttpRequestV3 implements INodeType {
|
|||||||
};
|
};
|
||||||
authDataKeys.auth = ['pass'];
|
authDataKeys.auth = ['pass'];
|
||||||
}
|
}
|
||||||
|
if (httpBearerAuth !== undefined) {
|
||||||
|
requestOptions.headers = requestOptions.headers ?? {};
|
||||||
|
requestOptions.headers.Authorization = `Bearer ${String(httpBearerAuth.token)}`;
|
||||||
|
authDataKeys.headers = ['Authorization'];
|
||||||
|
}
|
||||||
if (httpHeaderAuth !== undefined) {
|
if (httpHeaderAuth !== undefined) {
|
||||||
requestOptions.headers![httpHeaderAuth.name as string] = httpHeaderAuth.value;
|
requestOptions.headers![httpHeaderAuth.name as string] = httpHeaderAuth.value;
|
||||||
authDataKeys.headers = [httpHeaderAuth.name as string];
|
authDataKeys.headers = [httpHeaderAuth.name as string];
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import type { IExecuteFunctions, INodeTypeBaseDescription } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { HttpRequestV2 } from '../../V2/HttpRequestV2.node';
|
||||||
|
|
||||||
|
describe('HttpRequestV2', () => {
|
||||||
|
let node: HttpRequestV2;
|
||||||
|
let executeFunctions: IExecuteFunctions;
|
||||||
|
|
||||||
|
const baseUrl = 'http://example.com';
|
||||||
|
const options = {
|
||||||
|
redirect: '',
|
||||||
|
batching: { batch: { batchSize: 1, batchInterval: 1 } },
|
||||||
|
proxy: '',
|
||||||
|
timeout: '',
|
||||||
|
allowUnauthorizedCerts: '',
|
||||||
|
queryParameterArrays: '',
|
||||||
|
response: '',
|
||||||
|
lowercaseHeaders: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const baseDescription: INodeTypeBaseDescription = {
|
||||||
|
displayName: 'HTTP Request',
|
||||||
|
name: 'httpRequest',
|
||||||
|
description: 'Makes an HTTP request and returns the response data',
|
||||||
|
group: [],
|
||||||
|
};
|
||||||
|
node = new HttpRequestV2(baseDescription);
|
||||||
|
executeFunctions = {
|
||||||
|
getInputData: jest.fn(),
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
getNode: jest.fn(() => {
|
||||||
|
return {
|
||||||
|
type: 'n8n-nodes-base.httpRequest',
|
||||||
|
typeVersion: 2,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
getCredentials: jest.fn(),
|
||||||
|
helpers: {
|
||||||
|
request: jest.fn(),
|
||||||
|
requestOAuth1: jest.fn(
|
||||||
|
async () =>
|
||||||
|
await Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
requestOAuth2: jest.fn(
|
||||||
|
async () =>
|
||||||
|
await Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
requestWithAuthentication: jest.fn(),
|
||||||
|
requestWithAuthenticationPaginated: jest.fn(),
|
||||||
|
assertBinaryData: jest.fn(),
|
||||||
|
getBinaryStream: jest.fn(),
|
||||||
|
getBinaryMetadata: jest.fn(),
|
||||||
|
binaryToString: jest.fn((buffer: Buffer) => {
|
||||||
|
return buffer.toString();
|
||||||
|
}),
|
||||||
|
prepareBinaryData: jest.fn(),
|
||||||
|
},
|
||||||
|
getContext: jest.fn(),
|
||||||
|
sendMessageToUI: jest.fn(),
|
||||||
|
continueOnFail: jest.fn(),
|
||||||
|
getMode: jest.fn(),
|
||||||
|
} as unknown as IExecuteFunctions;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Authentication Handling', () => {
|
||||||
|
const authenticationTypes = [
|
||||||
|
{
|
||||||
|
genericCredentialType: 'httpBasicAuth',
|
||||||
|
credentials: { user: 'username', password: 'password' },
|
||||||
|
authField: 'auth',
|
||||||
|
authValue: { user: 'username', pass: 'password' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genericCredentialType: 'httpBearerAuth',
|
||||||
|
credentials: { token: 'bearerToken123' },
|
||||||
|
authField: 'headers',
|
||||||
|
authValue: { Authorization: 'Bearer bearerToken123' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genericCredentialType: 'httpDigestAuth',
|
||||||
|
credentials: { user: 'username', password: 'password' },
|
||||||
|
authField: 'auth',
|
||||||
|
authValue: { user: 'username', pass: 'password', sendImmediately: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genericCredentialType: 'httpHeaderAuth',
|
||||||
|
credentials: { name: 'Authorization', value: 'Bearer token' },
|
||||||
|
authField: 'headers',
|
||||||
|
authValue: { Authorization: 'Bearer token' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genericCredentialType: 'httpQueryAuth',
|
||||||
|
credentials: { name: 'Token', value: 'secretToken' },
|
||||||
|
authField: 'qs',
|
||||||
|
authValue: { Token: 'secretToken' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genericCredentialType: 'oAuth1Api',
|
||||||
|
credentials: { oauth_token: 'token', oauth_token_secret: 'secret' },
|
||||||
|
authField: 'oauth',
|
||||||
|
authValue: { oauth_token: 'token', oauth_token_secret: 'secret' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
genericCredentialType: 'oAuth2Api',
|
||||||
|
credentials: { access_token: 'accessToken' },
|
||||||
|
authField: 'auth',
|
||||||
|
authValue: { bearer: 'accessToken' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(authenticationTypes)(
|
||||||
|
'should handle $genericCredentialType authentication',
|
||||||
|
async ({ genericCredentialType, credentials, authField, authValue }) => {
|
||||||
|
(executeFunctions.getInputData as jest.Mock).mockReturnValue([{ json: {} }]);
|
||||||
|
(executeFunctions.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
|
||||||
|
switch (paramName) {
|
||||||
|
case 'method':
|
||||||
|
return 'GET';
|
||||||
|
case 'url':
|
||||||
|
return baseUrl;
|
||||||
|
case 'authentication':
|
||||||
|
return 'genericCredentialType';
|
||||||
|
case 'genericAuthType':
|
||||||
|
return genericCredentialType;
|
||||||
|
case 'options':
|
||||||
|
return options;
|
||||||
|
case 'bodyParametersUi':
|
||||||
|
case 'headerParametersUi':
|
||||||
|
case 'queryParametersUi':
|
||||||
|
return { parameter: [] };
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(executeFunctions.getCredentials as jest.Mock).mockResolvedValue(credentials);
|
||||||
|
const response = {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
(executeFunctions.helpers.request as jest.Mock).mockResolvedValue(response);
|
||||||
|
|
||||||
|
const result = await node.execute.call(executeFunctions);
|
||||||
|
expect(result).toEqual([[{ json: { success: true }, pairedItem: { item: 0 } }]]);
|
||||||
|
if (genericCredentialType === 'oAuth1Api') {
|
||||||
|
expect(executeFunctions.helpers.requestOAuth1).toHaveBeenCalled();
|
||||||
|
} else if (genericCredentialType === 'oAuth2Api') {
|
||||||
|
expect(executeFunctions.helpers.requestOAuth2).toHaveBeenCalled();
|
||||||
|
} else {
|
||||||
|
expect(executeFunctions.helpers.request).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
[authField]: expect.objectContaining(authValue),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
|
||||||
import type { IExecuteFunctions, INodeTypeBaseDescription } from 'n8n-workflow';
|
import type { IExecuteFunctions, INodeTypeBaseDescription } from 'n8n-workflow';
|
||||||
|
|
||||||
import { HttpRequestV3 } from '../../V3/HttpRequestV3.node';
|
import { HttpRequestV3 } from '../../V3/HttpRequestV3.node';
|
||||||
@@ -149,6 +148,12 @@ describe('HttpRequestV3', () => {
|
|||||||
authField: 'auth',
|
authField: 'auth',
|
||||||
authValue: { user: 'username', pass: 'password' },
|
authValue: { user: 'username', pass: 'password' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
genericCredentialType: 'httpBearerAuth',
|
||||||
|
credentials: { token: 'bearerToken123' },
|
||||||
|
authField: 'headers',
|
||||||
|
authValue: { Authorization: 'Bearer bearerToken123' },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
genericCredentialType: 'httpDigestAuth',
|
genericCredentialType: 'httpDigestAuth',
|
||||||
credentials: { user: 'username', password: 'password' },
|
credentials: { user: 'username', password: 'password' },
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
|
||||||
import { N8nTrigger } from '../N8nTrigger.node';
|
import { N8nTrigger } from '../N8nTrigger.node';
|
||||||
|
|
||||||
describe('N8nTrigger', () => {
|
describe('N8nTrigger', () => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import type { IExecuteFunctions, INodeExecutionData, INodeTypeBaseDescription } from 'n8n-workflow';
|
import type { IExecuteFunctions, INodeExecutionData, INodeTypeBaseDescription } from 'n8n-workflow';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user