mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-13 16:14:07 -08:00
fix(core): Allow ignoring SSL issues on generic oauth2 credentials (#6702)
This commit is contained in:
parent
db3c12ffc7
commit
feac369f6c
|
@ -4,7 +4,9 @@
|
||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import * as qs from 'querystring';
|
import * as qs from 'querystring';
|
||||||
|
import { Agent } from 'https';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import { getAuthError } from './utils';
|
import { getAuthError } from './utils';
|
||||||
import type { ClientOAuth2TokenData } from './ClientOAuth2Token';
|
import type { ClientOAuth2TokenData } from './ClientOAuth2Token';
|
||||||
import { ClientOAuth2Token } from './ClientOAuth2Token';
|
import { ClientOAuth2Token } from './ClientOAuth2Token';
|
||||||
|
@ -18,6 +20,7 @@ export interface ClientOAuth2RequestObject {
|
||||||
body?: Record<string, any>;
|
body?: Record<string, any>;
|
||||||
query?: qs.ParsedUrlQuery;
|
query?: qs.ParsedUrlQuery;
|
||||||
headers?: Headers;
|
headers?: Headers;
|
||||||
|
ignoreSSLIssues?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientOAuth2Options {
|
export interface ClientOAuth2Options {
|
||||||
|
@ -32,6 +35,7 @@ export interface ClientOAuth2Options {
|
||||||
state?: string;
|
state?: string;
|
||||||
body?: Record<string, any>;
|
body?: Record<string, any>;
|
||||||
query?: qs.ParsedUrlQuery;
|
query?: qs.ParsedUrlQuery;
|
||||||
|
ignoreSSLIssues?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResponseError extends Error {
|
class ResponseError extends Error {
|
||||||
|
@ -40,6 +44,8 @@ class ResponseError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sslIgnoringAgent = new Agent({ rejectUnauthorized: false });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an object that can handle the multiple OAuth 2.0 flows.
|
* Construct an object that can handle the multiple OAuth 2.0 flows.
|
||||||
*/
|
*/
|
||||||
|
@ -86,7 +92,7 @@ export class ClientOAuth2 {
|
||||||
url += (url.indexOf('?') === -1 ? '?' : '&') + query;
|
url += (url.indexOf('?') === -1 ? '?' : '&') + query;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.request({
|
const requestConfig: AxiosRequestConfig = {
|
||||||
url,
|
url,
|
||||||
method: options.method,
|
method: options.method,
|
||||||
data: qs.stringify(options.body),
|
data: qs.stringify(options.body),
|
||||||
|
@ -95,7 +101,13 @@ export class ClientOAuth2 {
|
||||||
// Axios rejects the promise by default for all status codes 4xx.
|
// Axios rejects the promise by default for all status codes 4xx.
|
||||||
// We override this to reject promises only on 5xxs
|
// We override this to reject promises only on 5xxs
|
||||||
validateStatus: (status) => status < 500,
|
validateStatus: (status) => status < 500,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (options.ignoreSSLIssues) {
|
||||||
|
requestConfig.httpsAgent = sslIgnoringAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.request(requestConfig);
|
||||||
|
|
||||||
const body = this.parseResponseBody<T>(response.data);
|
const body = this.parseResponseBody<T>(response.data);
|
||||||
|
|
||||||
|
|
|
@ -53,13 +53,13 @@ export class CodeFlow {
|
||||||
* the user access token.
|
* the user access token.
|
||||||
*/
|
*/
|
||||||
async getToken(
|
async getToken(
|
||||||
uri: string | URL,
|
urlString: string,
|
||||||
opts?: Partial<ClientOAuth2Options>,
|
opts?: Partial<ClientOAuth2Options>,
|
||||||
): Promise<ClientOAuth2Token> {
|
): Promise<ClientOAuth2Token> {
|
||||||
const options = { ...this.client.options, ...opts };
|
const options: ClientOAuth2Options = { ...this.client.options, ...opts };
|
||||||
expects(options, 'clientId', 'accessTokenUri');
|
expects(options, 'clientId', 'accessTokenUri');
|
||||||
|
|
||||||
const url = uri instanceof URL ? uri : new URL(uri, DEFAULT_URL_BASE);
|
const url = new URL(urlString, DEFAULT_URL_BASE);
|
||||||
if (
|
if (
|
||||||
typeof options.redirectUri === 'string' &&
|
typeof options.redirectUri === 'string' &&
|
||||||
typeof url.pathname === 'string' &&
|
typeof url.pathname === 'string' &&
|
||||||
|
@ -70,7 +70,7 @@ export class CodeFlow {
|
||||||
|
|
||||||
if (!url.search?.substring(1)) {
|
if (!url.search?.substring(1)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
throw new TypeError(`Unable to process uri: ${uri.toString()}`);
|
throw new TypeError(`Unable to process uri: ${urlString}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data =
|
const data =
|
||||||
|
|
|
@ -63,14 +63,15 @@ export function auth(username: string, password: string): string {
|
||||||
*/
|
*/
|
||||||
export function getRequestOptions(
|
export function getRequestOptions(
|
||||||
{ url, method, body, query, headers }: ClientOAuth2RequestObject,
|
{ url, method, body, query, headers }: ClientOAuth2RequestObject,
|
||||||
options: any,
|
options: ClientOAuth2Options,
|
||||||
): ClientOAuth2RequestObject {
|
): ClientOAuth2RequestObject {
|
||||||
const rOptions = {
|
const rOptions = {
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
body: { ...body, ...options.body },
|
body: { ...body, ...options.body },
|
||||||
query: { ...query, ...options.query },
|
query: { ...query, ...options.query },
|
||||||
headers: { ...headers, ...options.headers },
|
headers: headers ?? {},
|
||||||
|
ignoreSSLIssues: options.ignoreSSLIssues,
|
||||||
};
|
};
|
||||||
// if request authorization was overridden delete it from header
|
// if request authorization was overridden delete it from header
|
||||||
if (rOptions.headers.Authorization === '') {
|
if (rOptions.headers.Authorization === '') {
|
||||||
|
|
|
@ -265,6 +265,7 @@ oauth2CredentialController.get(
|
||||||
redirectUri: `${getInstanceBaseUrl()}/${restEndpoint}/oauth2-credential/callback`,
|
redirectUri: `${getInstanceBaseUrl()}/${restEndpoint}/oauth2-credential/callback`,
|
||||||
scopes: split(scopes, ','),
|
scopes: split(scopes, ','),
|
||||||
scopesSeparator: scopes.includes(',') ? ',' : ' ',
|
scopesSeparator: scopes.includes(',') ? ',' : ' ',
|
||||||
|
ignoreSSLIssues: get(oauthCredentials, 'ignoreSSLIssues') as boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (oauthCredentials.grantType === 'pkce') {
|
if (oauthCredentials.grantType === 'pkce') {
|
||||||
|
|
|
@ -1092,6 +1092,7 @@ export async function requestOAuth2(
|
||||||
clientSecret: credentials.clientSecret as string,
|
clientSecret: credentials.clientSecret as string,
|
||||||
accessTokenUri: credentials.accessTokenUrl as string,
|
accessTokenUri: credentials.accessTokenUrl as string,
|
||||||
scopes: (credentials.scope as string).split(' '),
|
scopes: (credentials.scope as string).split(' '),
|
||||||
|
ignoreSSLIssues: credentials.ignoreSSLIssues as boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
let oauthTokenData = credentials.oauthTokenData as ClientOAuth2TokenData;
|
let oauthTokenData = credentials.oauthTokenData as ClientOAuth2TokenData;
|
||||||
|
@ -1131,6 +1132,9 @@ export async function requestOAuth2(
|
||||||
},
|
},
|
||||||
oAuth2Options?.tokenType || oauthTokenData.tokenType,
|
oAuth2Options?.tokenType || oauthTokenData.tokenType,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
(requestOptions as OptionsWithUri).rejectUnauthorized = !credentials.ignoreSSLIssues;
|
||||||
|
|
||||||
// Signs the request by adding authorization headers or query parameters depending
|
// Signs the request by adding authorization headers or query parameters depending
|
||||||
// on the token-type used.
|
// on the token-type used.
|
||||||
const newRequestOptions = token.sign(requestOptions as ClientOAuth2RequestObject);
|
const newRequestOptions = token.sign(requestOptions as ClientOAuth2RequestObject);
|
||||||
|
|
|
@ -104,5 +104,12 @@ export class OAuth2Api implements ICredentialType {
|
||||||
],
|
],
|
||||||
default: 'header',
|
default: 'header',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Ignore SSL Issues',
|
||||||
|
name: 'ignoreSSLIssues',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
doNotInherit: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1116,6 +1116,7 @@ export interface INodeProperties {
|
||||||
extractValue?: INodePropertyValueExtractor;
|
extractValue?: INodePropertyValueExtractor;
|
||||||
modes?: INodePropertyMode[];
|
modes?: INodePropertyMode[];
|
||||||
requiresDataPath?: 'single' | 'multiple';
|
requiresDataPath?: 'single' | 'multiple';
|
||||||
|
doNotInherit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodePropertyModeTypeOptions {
|
export interface INodePropertyModeTypeOptions {
|
||||||
|
|
|
@ -1601,6 +1601,8 @@ export function mergeNodeProperties(
|
||||||
): void {
|
): void {
|
||||||
let existingIndex: number;
|
let existingIndex: number;
|
||||||
for (const property of addProperties) {
|
for (const property of addProperties) {
|
||||||
|
if (property.doNotInherit) continue;
|
||||||
|
|
||||||
existingIndex = mainProperties.findIndex((element) => element.name === property.name);
|
existingIndex = mainProperties.findIndex((element) => element.name === property.name);
|
||||||
|
|
||||||
if (existingIndex === -1) {
|
if (existingIndex === -1) {
|
||||||
|
|
Loading…
Reference in a new issue