diff --git a/packages/@n8n/client-oauth2/package.json b/packages/@n8n/client-oauth2/package.json index 1f27d8632d..a75c1bafbe 100644 --- a/packages/@n8n/client-oauth2/package.json +++ b/packages/@n8n/client-oauth2/package.json @@ -20,6 +20,6 @@ "dist/**/*" ], "dependencies": { - "axios": "0.21.4" + "axios": "1.6.2" } } diff --git a/packages/cli/package.json b/packages/cli/package.json index 7d7d0e6288..62785cd5c2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -108,7 +108,7 @@ "@rudderstack/rudder-sdk-node": "1.0.6", "@sentry/integrations": "7.87.0", "@sentry/node": "7.87.0", - "axios": "0.21.4", + "axios": "1.6.2", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "bull": "4.10.2", diff --git a/packages/cli/src/ExternalSecrets/providers/vault.ts b/packages/cli/src/ExternalSecrets/providers/vault.ts index 6156d762bd..6b1046402a 100644 --- a/packages/cli/src/ExternalSecrets/providers/vault.ts +++ b/packages/cli/src/ExternalSecrets/providers/vault.ts @@ -251,19 +251,15 @@ export class VaultProvider extends SecretsProvider { this.#http = axios.create({ baseURL: baseURL.toString() }); if (this.settings.namespace) { this.#http.interceptors.request.use((config) => { - return { - ...config, - // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment - headers: { ...config.headers, 'X-Vault-Namespace': this.settings.namespace }, - }; + config.headers['X-Vault-Namespace'] = this.settings.namespace; + return config; }); } this.#http.interceptors.request.use((config) => { - if (!this.#currentToken) { - return config; + if (this.#currentToken) { + config.headers['X-Vault-Token'] = this.#currentToken; } - // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment - return { ...config, headers: { ...config.headers, 'X-Vault-Token': this.#currentToken } }; + return config; }); } diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts b/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts index 78377d623a..932ac90c04 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts +++ b/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts @@ -324,9 +324,15 @@ export class MessageEventBusDestinationWebhook password: httpBasicAuth.password as string, }; } else if (httpHeaderAuth) { - this.axiosRequestOptions.headers[httpHeaderAuth.name as string] = httpHeaderAuth.value; + this.axiosRequestOptions.headers = { + ...this.axiosRequestOptions.headers, + [httpHeaderAuth.name as string]: httpHeaderAuth.value as string, + }; } else if (httpQueryAuth) { - this.axiosRequestOptions.params[httpQueryAuth.name as string] = httpQueryAuth.value; + this.axiosRequestOptions.params = { + ...this.axiosRequestOptions.params, + [httpQueryAuth.name as string]: httpQueryAuth.value as string, + }; } else if (httpDigestAuth) { this.axiosRequestOptions.auth = { username: httpDigestAuth.user as string, diff --git a/packages/core/package.json b/packages/core/package.json index 187dc2bc29..857bf0d9b5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,7 +50,7 @@ "dependencies": { "@n8n/client-oauth2": "workspace:*", "aws4": "1.11.0", - "axios": "0.21.4", + "axios": "1.6.2", "concat-stream": "2.0.0", "cron": "1.7.2", "fast-glob": "3.2.12", diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index fcf53d7b6e..3fa6dd4e61 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -16,6 +16,7 @@ import type { import { ClientOAuth2 } from '@n8n/client-oauth2'; import type { AxiosError, + AxiosHeaders, AxiosPromise, AxiosProxyConfig, AxiosRequestConfig, @@ -186,23 +187,24 @@ const createFormDataObject = (data: Record) => { }); return formData; }; -function searchForHeader(headers: IDataObject, headerName: string) { - if (headers === undefined) { + +function searchForHeader(config: AxiosRequestConfig, headerName: string) { + if (config.headers === undefined) { return undefined; } - const headerNames = Object.keys(headers); + const headerNames = Object.keys(config.headers); headerName = headerName.toLowerCase(); return headerNames.find((thisHeader) => thisHeader.toLowerCase() === headerName); } -async function generateContentLengthHeader(formData: FormData, headers: IDataObject) { - if (!formData?.getLength) { +async function generateContentLengthHeader(config: AxiosRequestConfig) { + if (!(config.data instanceof FormData)) { return; } try { - const length = await new Promise((res, rej) => { - formData.getLength((error: Error | null, length: number) => { + const length = await new Promise((res, rej) => { + config.data.getLength((error: Error | null, length: number) => { if (error) { rej(error); return; @@ -210,9 +212,10 @@ async function generateContentLengthHeader(formData: FormData, headers: IDataObj res(length); }); }); - headers = Object.assign(headers, { + config.headers = { + ...config.headers, 'content-length': length, - }); + }; } catch (error) { Logger.error('Unable to calculate form data length', { error }); } @@ -228,7 +231,7 @@ async function parseRequestObject(requestObject: IDataObject) { const axiosConfig: AxiosRequestConfig = {}; if (requestObject.headers !== undefined) { - axiosConfig.headers = requestObject.headers as string; + axiosConfig.headers = requestObject.headers as AxiosHeaders; } // Let's start parsing the hardest part, which is the request body. @@ -246,7 +249,7 @@ async function parseRequestObject(requestObject: IDataObject) { ); const contentType = contentTypeHeaderKeyName && - (axiosConfig.headers[contentTypeHeaderKeyName] as string | undefined); + (axiosConfig.headers?.[contentTypeHeaderKeyName] as string | undefined); if (contentType === 'application/x-www-form-urlencoded' && requestObject.formData === undefined) { // there are nodes incorrectly created, informing the content type header // and also using formData. Request lib takes precedence for the formData. @@ -265,7 +268,7 @@ async function parseRequestObject(requestObject: IDataObject) { axiosConfig.data = stringify(allData); } } - } else if (contentType && contentType.includes('multipart/form-data') !== false) { + } else if (contentType?.includes('multipart/form-data')) { if (requestObject.formData !== undefined && requestObject.formData instanceof FormData) { axiosConfig.data = requestObject.formData; } else { @@ -278,10 +281,10 @@ async function parseRequestObject(requestObject: IDataObject) { } // replace the existing header with a new one that // contains the boundary property. - delete axiosConfig.headers[contentTypeHeaderKeyName]; + delete axiosConfig.headers?.[contentTypeHeaderKeyName!]; const headers = axiosConfig.data.getHeaders(); axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers); - await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers); + await generateContentLengthHeader(axiosConfig); } else { // When using the `form` property it means the content should be x-www-form-urlencoded. if (requestObject.form !== undefined && requestObject.body === undefined) { @@ -291,7 +294,7 @@ async function parseRequestObject(requestObject: IDataObject) { ? stringify(requestObject.form, { format: 'RFC3986' }) : stringify(requestObject.form).toString(); if (axiosConfig.headers !== undefined) { - const headerName = searchForHeader(axiosConfig.headers, 'content-type'); + const headerName = searchForHeader(axiosConfig, 'content-type'); if (headerName) { delete axiosConfig.headers[headerName]; } @@ -305,9 +308,11 @@ async function parseRequestObject(requestObject: IDataObject) { // remove any "content-type" that might exist. if (axiosConfig.headers !== undefined) { const headers = Object.keys(axiosConfig.headers); - headers.forEach((header) => - header.toLowerCase() === 'content-type' ? delete axiosConfig.headers[header] : null, - ); + headers.forEach((header) => { + if (header.toLowerCase() === 'content-type') { + delete axiosConfig.headers?.[header]; + } + }); } if (requestObject.formData instanceof FormData) { @@ -318,7 +323,7 @@ async function parseRequestObject(requestObject: IDataObject) { // Mix in headers as FormData creates the boundary. const headers = axiosConfig.data.getHeaders(); axiosConfig.headers = Object.assign(axiosConfig.headers || {}, headers); - await generateContentLengthHeader(axiosConfig.data, axiosConfig.headers); + await generateContentLengthHeader(axiosConfig); } else if (requestObject.body !== undefined) { // If we have body and possibly form if (requestObject.form !== undefined && requestObject.body) { @@ -755,7 +760,7 @@ export async function proxyRequestToAxios( return configObject.resolveWithFullResponse ? { body, - headers: response.headers, + headers: { ...response.headers }, statusCode: response.status, statusMessage: response.statusText, request: response.request, @@ -852,7 +857,7 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest const { body } = n8nRequest; if (body) { // Let's add some useful header standards here. - const existingContentTypeHeaderKey = searchForHeader(axiosRequest.headers, 'content-type'); + const existingContentTypeHeaderKey = searchForHeader(axiosRequest, 'content-type'); if (existingContentTypeHeaderKey === undefined) { axiosRequest.headers = axiosRequest.headers || {}; // We are only setting content type headers if the user did @@ -866,7 +871,7 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest axiosRequest.headers['Content-Type'] = 'application/x-www-form-urlencoded'; } } else if ( - axiosRequest.headers[existingContentTypeHeaderKey] === 'application/x-www-form-urlencoded' + axiosRequest.headers?.[existingContentTypeHeaderKey] === 'application/x-www-form-urlencoded' ) { axiosRequest.data = new URLSearchParams(n8nRequest.body as Record); } @@ -879,19 +884,25 @@ function convertN8nRequestToAxios(n8nRequest: IHttpRequestOptions): AxiosRequest } if (n8nRequest.json) { - const key = searchForHeader(axiosRequest.headers, 'accept'); + const key = searchForHeader(axiosRequest, 'accept'); // If key exists, then the user has set both accept // header and the json flag. Header should take precedence. if (!key) { - axiosRequest.headers.Accept = 'application/json'; + axiosRequest.headers = { + ...axiosRequest.headers, + Accept: 'application/json', + }; } } - const userAgentHeader = searchForHeader(axiosRequest.headers, 'user-agent'); + const userAgentHeader = searchForHeader(axiosRequest, 'user-agent'); // If key exists, then the user has set both accept // header and the json flag. Header should take precedence. if (!userAgentHeader) { - axiosRequest.headers['User-Agent'] = 'n8n'; + axiosRequest.headers = { + ...axiosRequest.headers, + 'User-Agent': 'n8n', + }; } if (n8nRequest.ignoreHttpStatusErrors) { diff --git a/packages/core/src/ObjectStore/ObjectStore.service.ee.ts b/packages/core/src/ObjectStore/ObjectStore.service.ee.ts index fb20760f58..bf2757b655 100644 --- a/packages/core/src/ObjectStore/ObjectStore.service.ee.ts +++ b/packages/core/src/ObjectStore/ObjectStore.service.ee.ts @@ -7,17 +7,18 @@ import { sign } from 'aws4'; import { isStream, parseXml, writeBlockedMessage } from './utils'; import { ApplicationError, LoggerProxy as Logger } from 'n8n-workflow'; -import type { AxiosRequestConfig, AxiosResponse, Method } from 'axios'; +import type { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig, Method } from 'axios'; import type { Request as Aws4Options, Credentials as Aws4Credentials } from 'aws4'; import type { Bucket, ConfigSchemaCredentials, ListPage, + MetadataResponseHeaders, RawListPage, RequestOptions, } from './types'; import type { Readable } from 'stream'; -import type { BinaryData } from '..'; +import type { BinaryData } from '../BinaryData/types'; @Service() export class ObjectStoreService { @@ -115,19 +116,11 @@ export class ObjectStoreService { * @doc https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html */ async getMetadata(fileId: string) { - type Response = { - headers: { - 'content-length': string; - 'content-type'?: string; - 'x-amz-meta-filename'?: string; - } & BinaryData.PreWriteMetadata; - }; - const path = `${this.bucket.name}/${fileId}`; - const response: Response = await this.request('HEAD', this.host, path); + const response = await this.request('HEAD', this.host, path); - return response.headers; + return response.headers as MetadataResponseHeaders; } /** @@ -239,10 +232,16 @@ export class ObjectStoreService { this.logger.warn(logMessage); - return { status: 403, statusText: 'Forbidden', data: logMessage, headers: {}, config: {} }; + return { + status: 403, + statusText: 'Forbidden', + data: logMessage, + headers: {}, + config: {} as InternalAxiosRequestConfig, + }; } - private async request( + private async request( method: Method, host: string, rawPath = '', @@ -275,7 +274,7 @@ export class ObjectStoreService { try { this.logger.debug('Sending request to S3', { config }); - return await axios.request(config); + return await axios.request(config); } catch (e) { const error = e instanceof Error ? e : new Error(`${e}`); diff --git a/packages/core/src/ObjectStore/types.ts b/packages/core/src/ObjectStore/types.ts index 444630e924..639ae2e6a2 100644 --- a/packages/core/src/ObjectStore/types.ts +++ b/packages/core/src/ObjectStore/types.ts @@ -1,4 +1,5 @@ -import type { ResponseType } from 'axios'; +import type { AxiosResponseHeaders, ResponseType } from 'axios'; +import type { BinaryData } from '../BinaryData/types'; export type RawListPage = { listBucketResult: { @@ -31,4 +32,10 @@ export type RequestOptions = { responseType?: ResponseType; }; +export type MetadataResponseHeaders = AxiosResponseHeaders & { + 'content-length': string; + 'content-type'?: string; + 'x-amz-meta-filename'?: string; +} & BinaryData.PreWriteMetadata; + export type ConfigSchemaCredentials = { accessKey: string; accessSecret: string }; diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index e9352f2510..6fdd1aaa55 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -50,7 +50,7 @@ "@n8n/permissions": "workspace:*", "@vueuse/components": "^10.5.0", "@vueuse/core": "^10.5.0", - "axios": "^0.21.1", + "axios": "^1.6.2", "chart.js": "^4.4.0", "codemirror-lang-html-n8n": "^1.0.0", "codemirror-lang-n8n-expression": "^0.2.0", diff --git a/packages/editor-ui/src/utils/apiUtils.ts b/packages/editor-ui/src/utils/apiUtils.ts index ce3c46179c..d8ef8719f3 100644 --- a/packages/editor-ui/src/utils/apiUtils.ts +++ b/packages/editor-ui/src/utils/apiUtils.ts @@ -43,6 +43,21 @@ class ResponseError extends Error { } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const legacyParamSerializer = (params: Record) => + Object.keys(params) + .filter((key) => params[key] !== undefined) + .map((key) => { + if (Array.isArray(params[key])) { + return params[key].map((v) => `${key}[]=${encodeURIComponent(v)}`).join('&'); + } + if (typeof params[key] === 'object') { + params[key] = JSON.stringify(params[key]); + } + return `${key}=${encodeURIComponent(params[key])}`; + }) + .join('&'); + export async function request(config: { method: Method; baseURL: string; @@ -67,8 +82,9 @@ export async function request(config: { } if (['POST', 'PATCH', 'PUT'].includes(method)) { options.data = data; - } else { + } else if (data) { options.params = data; + options.paramsSerializer = legacyParamSerializer; } try { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5069628119..f68a8d1436 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -259,8 +259,8 @@ importers: packages/@n8n/client-oauth2: dependencies: axios: - specifier: 0.21.4 - version: 0.21.4 + specifier: 1.6.2 + version: 1.6.2 packages/@n8n/nodes-langchain: dependencies: @@ -460,8 +460,8 @@ importers: specifier: 7.87.0 version: 7.87.0 axios: - specifier: 0.21.4 - version: 0.21.4 + specifier: 1.6.2 + version: 1.6.2 basic-auth: specifier: 2.0.1 version: 2.0.1 @@ -818,8 +818,8 @@ importers: specifier: 1.11.0 version: 1.11.0 axios: - specifier: 0.21.4 - version: 0.21.4 + specifier: 1.6.2 + version: 1.6.2 concat-stream: specifier: 2.0.0 version: 2.0.0 @@ -1093,8 +1093,8 @@ importers: specifier: ^10.5.0 version: 10.5.0(vue@3.3.4) axios: - specifier: ^0.21.1 - version: 0.21.4 + specifier: ^1.6.2 + version: 1.6.2 chart.js: specifier: ^4.4.0 version: 4.4.0 @@ -11389,8 +11389,8 @@ packages: transitivePeerDependencies: - debug - /axios@1.4.0: - resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} + /axios@1.6.2: + resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==} dependencies: follow-redirects: 1.15.2(debug@4.3.4) form-data: 4.0.0 @@ -16246,7 +16246,7 @@ packages: /infisical-node@1.3.0: resolution: {integrity: sha512-tTnnExRAO/ZyqiRdnSlBisErNToYWgtunMWh+8opClEt5qjX7l6HC/b4oGo2AuR2Pf41IR+oqo+dzkM1TCvlUA==} dependencies: - axios: 1.4.0 + axios: 1.6.2 dotenv: 16.3.1 tweetnacl: 1.0.3 tweetnacl-util: 0.15.1