mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
fix(core): Do not add Authentication header when authentication type is body (#8201)
This commit is contained in:
committed by
GitHub
parent
ccb2b076f8
commit
ac1c642fdd
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import * as qs from 'querystring';
|
||||
import { Agent } from 'https';
|
||||
@@ -26,6 +25,7 @@ export interface ClientOAuth2Options {
|
||||
clientId: string;
|
||||
clientSecret?: string;
|
||||
accessTokenUri: string;
|
||||
authentication?: 'header' | 'body';
|
||||
authorizationUri?: string;
|
||||
redirectUri?: string;
|
||||
scopes?: string[];
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { ClientOAuth2, ClientOAuth2Options } from './ClientOAuth2';
|
||||
import type { ClientOAuth2 } from './ClientOAuth2';
|
||||
import type { ClientOAuth2Token, ClientOAuth2TokenData } from './ClientOAuth2Token';
|
||||
import { DEFAULT_HEADERS } from './constants';
|
||||
import type { Headers } from './types';
|
||||
import { auth, expects, getRequestOptions } from './utils';
|
||||
|
||||
interface CredentialsFlowBody {
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
grant_type: 'client_credentials';
|
||||
scope?: string;
|
||||
}
|
||||
@@ -19,10 +22,11 @@ export class CredentialsFlow {
|
||||
/**
|
||||
* Request an access token using the client credentials.
|
||||
*/
|
||||
async getToken(opts?: Partial<ClientOAuth2Options>): Promise<ClientOAuth2Token> {
|
||||
const options = { ...this.client.options, ...opts };
|
||||
async getToken(): Promise<ClientOAuth2Token> {
|
||||
const options = { ...this.client.options };
|
||||
expects(options, 'clientId', 'clientSecret', 'accessTokenUri');
|
||||
|
||||
const headers: Headers = { ...DEFAULT_HEADERS };
|
||||
const body: CredentialsFlowBody = {
|
||||
grant_type: 'client_credentials',
|
||||
};
|
||||
@@ -31,15 +35,21 @@ export class CredentialsFlow {
|
||||
body.scope = options.scopes.join(options.scopesSeparator ?? ' ');
|
||||
}
|
||||
|
||||
const clientId = options.clientId;
|
||||
const clientSecret = options.clientSecret;
|
||||
|
||||
if (options.authentication === 'body') {
|
||||
body.client_id = clientId;
|
||||
body.client_secret = clientSecret;
|
||||
} else {
|
||||
headers.Authorization = auth(clientId, clientSecret);
|
||||
}
|
||||
|
||||
const requestOptions = getRequestOptions(
|
||||
{
|
||||
url: options.accessTokenUri,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...DEFAULT_HEADERS,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
Authorization: auth(options.clientId, options.clientSecret),
|
||||
},
|
||||
headers,
|
||||
body,
|
||||
},
|
||||
options,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { ClientOAuth2, ClientOAuth2Options, ClientOAuth2RequestObject } from './ClientOAuth2';
|
||||
export { ClientOAuth2Token, ClientOAuth2TokenData } from './ClientOAuth2Token';
|
||||
export type * from './types';
|
||||
|
||||
@@ -1 +1,19 @@
|
||||
export type Headers = Record<string, string | string[]>;
|
||||
|
||||
export type OAuth2GrantType = 'pkce' | 'authorizationCode' | 'clientCredentials';
|
||||
|
||||
export interface OAuth2CredentialData {
|
||||
clientId: string;
|
||||
clientSecret?: string;
|
||||
accessTokenUrl: string;
|
||||
authentication?: 'header' | 'body';
|
||||
authUrl?: string;
|
||||
scope?: string;
|
||||
authQueryParameters?: string;
|
||||
grantType: OAuth2GrantType;
|
||||
ignoreSSLIssues?: boolean;
|
||||
oauthTokenData?: {
|
||||
access_token: string;
|
||||
refresh_token?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import nock from 'nock';
|
||||
import type { Headers } from '../src/types';
|
||||
import type { ClientOAuth2Options } from '../src';
|
||||
import { ClientOAuth2, ClientOAuth2Token } from '../src';
|
||||
import * as config from './config';
|
||||
|
||||
@@ -11,18 +13,24 @@ describe('CredentialsFlow', () => {
|
||||
nock.restore();
|
||||
});
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
describe('#getToken', () => {
|
||||
const createAuthClient = (scopes?: string[]) =>
|
||||
const createAuthClient = ({
|
||||
scopes,
|
||||
authentication,
|
||||
}: Pick<ClientOAuth2Options, 'scopes' | 'authentication'> = {}) =>
|
||||
new ClientOAuth2({
|
||||
clientId: config.clientId,
|
||||
clientSecret: config.clientSecret,
|
||||
accessTokenUri: config.accessTokenUri,
|
||||
authentication,
|
||||
authorizationGrants: ['credentials'],
|
||||
scopes,
|
||||
});
|
||||
|
||||
const mockTokenCall = (requestedScope?: string) =>
|
||||
nock(config.baseUrl)
|
||||
const mockTokenCall = async ({ requestedScope }: { requestedScope?: string } = {}) => {
|
||||
const nockScope = nock(config.baseUrl)
|
||||
.post(
|
||||
'/login/oauth/access_token',
|
||||
({ scope, grant_type }) =>
|
||||
@@ -34,10 +42,19 @@ describe('CredentialsFlow', () => {
|
||||
refresh_token: config.refreshToken,
|
||||
scope: requestedScope,
|
||||
});
|
||||
return new Promise<{ headers: Headers; body: unknown }>((resolve) => {
|
||||
nockScope.once('request', (req) => {
|
||||
resolve({
|
||||
headers: req.headers,
|
||||
body: req.requestBodyBuffers.toString('utf-8'),
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
it('should request the token', async () => {
|
||||
const authClient = createAuthClient(['notifications']);
|
||||
mockTokenCall('notifications');
|
||||
const authClient = createAuthClient({ scopes: ['notifications'] });
|
||||
const requestPromise = mockTokenCall({ requestedScope: 'notifications' });
|
||||
|
||||
const user = await authClient.credentials.getToken();
|
||||
|
||||
@@ -45,34 +62,62 @@ describe('CredentialsFlow', () => {
|
||||
expect(user.accessToken).toEqual(config.accessToken);
|
||||
expect(user.tokenType).toEqual('bearer');
|
||||
expect(user.data.scope).toEqual('notifications');
|
||||
|
||||
const { headers, body } = await requestPromise;
|
||||
expect(headers.authorization).toBe('Basic YWJjOjEyMw==');
|
||||
expect(body).toEqual('grant_type=client_credentials&scope=notifications');
|
||||
});
|
||||
|
||||
it('when scopes are undefined, it should not send scopes to an auth server', async () => {
|
||||
const authClient = createAuthClient();
|
||||
mockTokenCall();
|
||||
const requestPromise = mockTokenCall();
|
||||
|
||||
const user = await authClient.credentials.getToken();
|
||||
expect(user).toBeInstanceOf(ClientOAuth2Token);
|
||||
expect(user.accessToken).toEqual(config.accessToken);
|
||||
expect(user.tokenType).toEqual('bearer');
|
||||
expect(user.data.scope).toEqual(undefined);
|
||||
|
||||
const { body } = await requestPromise;
|
||||
expect(body).toEqual('grant_type=client_credentials');
|
||||
});
|
||||
|
||||
it('when scopes is an empty array, it should send empty scope string to an auth server', async () => {
|
||||
const authClient = createAuthClient([]);
|
||||
mockTokenCall('');
|
||||
const authClient = createAuthClient({ scopes: [] });
|
||||
const requestPromise = mockTokenCall({ requestedScope: '' });
|
||||
|
||||
const user = await authClient.credentials.getToken();
|
||||
expect(user).toBeInstanceOf(ClientOAuth2Token);
|
||||
expect(user.accessToken).toEqual(config.accessToken);
|
||||
expect(user.tokenType).toEqual('bearer');
|
||||
expect(user.data.scope).toEqual('');
|
||||
|
||||
const { body } = await requestPromise;
|
||||
expect(body).toEqual('grant_type=client_credentials&scope=');
|
||||
});
|
||||
|
||||
it('should handle authentication = "header"', async () => {
|
||||
const authClient = createAuthClient({ scopes: [] });
|
||||
const requestPromise = mockTokenCall({ requestedScope: '' });
|
||||
await authClient.credentials.getToken();
|
||||
const { headers, body } = await requestPromise;
|
||||
expect(headers?.authorization).toBe('Basic YWJjOjEyMw==');
|
||||
expect(body).toEqual('grant_type=client_credentials&scope=');
|
||||
});
|
||||
|
||||
it('should handle authentication = "body"', async () => {
|
||||
const authClient = createAuthClient({ scopes: [], authentication: 'body' });
|
||||
const requestPromise = mockTokenCall({ requestedScope: '' });
|
||||
await authClient.credentials.getToken();
|
||||
const { headers, body } = await requestPromise;
|
||||
expect(headers?.authorization).toBe(undefined);
|
||||
expect(body).toEqual('grant_type=client_credentials&scope=&client_id=abc&client_secret=123');
|
||||
});
|
||||
|
||||
describe('#sign', () => {
|
||||
it('should be able to sign a standard request object', async () => {
|
||||
const authClient = createAuthClient(['notifications']);
|
||||
mockTokenCall('notifications');
|
||||
const authClient = createAuthClient({ scopes: ['notifications'] });
|
||||
void mockTokenCall({ requestedScope: 'notifications' });
|
||||
|
||||
const token = await authClient.credentials.getToken();
|
||||
const requestOptions = token.sign({
|
||||
@@ -99,8 +144,8 @@ describe('CredentialsFlow', () => {
|
||||
});
|
||||
|
||||
it('should make a request to get a new access token', async () => {
|
||||
const authClient = createAuthClient(['notifications']);
|
||||
mockTokenCall('notifications');
|
||||
const authClient = createAuthClient({ scopes: ['notifications'] });
|
||||
void mockTokenCall({ requestedScope: 'notifications' });
|
||||
|
||||
const token = await authClient.credentials.getToken();
|
||||
expect(token.accessToken).toEqual(config.accessToken);
|
||||
|
||||
Reference in New Issue
Block a user