diff --git a/packages/cli/package.json b/packages/cli/package.json index 412f8a48f3b..75327ebcad3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -178,6 +178,7 @@ "sshpk": "1.18.0", "swagger-ui-express": "5.0.1", "syslog-client": "1.1.1", + "undici": "^7.16.0", "uuid": "catalog:", "validator": "13.7.0", "ws": "8.17.1", diff --git a/packages/cli/src/sso.ee/oidc/__tests__/oidc.service.ee.test.ts b/packages/cli/src/sso.ee/oidc/__tests__/oidc.service.ee.test.ts index cdcac542302..510d9f4a6de 100644 --- a/packages/cli/src/sso.ee/oidc/__tests__/oidc.service.ee.test.ts +++ b/packages/cli/src/sso.ee/oidc/__tests__/oidc.service.ee.test.ts @@ -1,3 +1,4 @@ +import type { OidcConfigDto } from '@n8n/api-types'; import type { Logger } from '@n8n/backend-common'; import { mockInstance, mockLogger } from '@n8n/backend-test-utils'; import type { GlobalConfig } from '@n8n/config'; @@ -5,20 +6,24 @@ import type { AuthIdentityRepository, SettingsRepository, UserRepository } from import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; import type { Cipher, InstanceSettings } from 'n8n-core'; - import * as client from 'openid-client'; - -import type { JwtService } from '@/services/jwt.service'; -import type { UrlService } from '@/services/url.service'; +import { EnvHttpProxyAgent } from 'undici'; import * as ssoHelpers from '../../sso-helpers'; import { OIDC_PREFERENCES_DB_KEY } from '../constants'; import { OidcService } from '../oidc.service.ee'; -import { Publisher } from '@/scaling/pubsub/publisher.service'; -import type { OidcConfigDto } from '@n8n/api-types'; -import { type ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee'; + import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; +import { type ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee'; +import { Publisher } from '@/scaling/pubsub/publisher.service'; +import type { JwtService } from '@/services/jwt.service'; +import type { UrlService } from '@/services/url.service'; + +jest.mock('undici', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + EnvHttpProxyAgent: jest.fn().mockImplementation(() => ({})), +})); describe('OidcService', () => { let oidcService: OidcService; @@ -537,4 +542,86 @@ describe('OidcService', () => { expect(user.email).toEqual('john.doe@test.com'); }); }); + + describe('proxy configuration', () => { + const originalEnv = process.env; + + // Helper function to create a proper mock Response + const createMockResponse = () => { + const mockData = { + issuer: 'https://example.com', + authorization_endpoint: 'https://example.com/auth', + token_endpoint: 'https://example.com/token', + userinfo_endpoint: 'https://example.com/userinfo', + jwks_uri: 'https://example.com/jwks', + }; + return new Response(JSON.stringify(mockData), { + status: 200, + // eslint-disable-next-line @typescript-eslint/naming-convention + headers: { 'content-type': 'application/json' }, + }); + }; + + beforeEach(() => { + // Reset environment before each test + process.env = { ...originalEnv }; + // Reset the mock between tests + (EnvHttpProxyAgent as unknown as jest.Mock).mockClear(); + }); + + afterEach(() => { + // Restore original environment after each test + process.env = originalEnv; + }); + + it.each([ + { envVar: 'HTTP_PROXY', value: 'http://proxy.example.com:8080' }, + { envVar: 'HTTPS_PROXY', value: 'https://proxy.example.com:8443' }, + { envVar: 'ALL_PROXY', value: 'http://all-proxy.example.com:8888' }, + ])('should instantiate EnvHttpProxyAgent when $envVar is set', async ({ envVar, value }) => { + // Set proxy environment variable + process.env[envVar] = value; + + const discoveryUrl = new URL('https://example.com/.well-known/openid-configuration'); + const clientId = 'test-client'; + const clientSecret = 'test-secret'; + + global.fetch = jest.fn().mockResolvedValue(createMockResponse()); + + // Call the private method directly using type assertion + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + await (oidcService as any).createProxyAwareConfiguration( + discoveryUrl, + clientId, + clientSecret, + ); + + // Verify EnvHttpProxyAgent was instantiated + expect(EnvHttpProxyAgent).toHaveBeenCalled(); + }); + + it('should not instantiate EnvHttpProxyAgent when no proxy env vars are set', async () => { + // Ensure no proxy env vars are set + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.ALL_PROXY; + + const discoveryUrl = new URL('https://example.com/.well-known/openid-configuration'); + const clientId = 'test-client'; + const clientSecret = 'test-secret'; + + global.fetch = jest.fn().mockResolvedValue(createMockResponse()); + + // Call the private method directly + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + await (oidcService as any).createProxyAwareConfiguration( + discoveryUrl, + clientId, + clientSecret, + ); + + // Should not instantiate EnvHttpProxyAgent when no proxy is configured + expect(EnvHttpProxyAgent).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/cli/src/sso.ee/oidc/oidc.service.ee.ts b/packages/cli/src/sso.ee/oidc/oidc.service.ee.ts index 6c0514d9825..f93a6dd6964 100644 --- a/packages/cli/src/sso.ee/oidc/oidc.service.ee.ts +++ b/packages/cli/src/sso.ee/oidc/oidc.service.ee.ts @@ -10,17 +10,13 @@ import { type User, UserRepository, } from '@n8n/db'; +import { OnPubSubEvent } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import { randomUUID } from 'crypto'; import { Cipher, InstanceSettings } from 'n8n-core'; import { jsonParse, UserError } from 'n8n-workflow'; import * as client from 'openid-client'; - -import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; -import { InternalServerError } from '@/errors/response-errors/internal-server.error'; -import { JwtService } from '@/services/jwt.service'; -import { UrlService } from '@/services/url.service'; +import { EnvHttpProxyAgent } from 'undici'; import { getCurrentAuthenticationMethod, @@ -30,8 +26,13 @@ import { setCurrentAuthenticationMethod, } from '../sso-helpers'; import { OIDC_CLIENT_SECRET_REDACTED_VALUE, OIDC_PREFERENCES_DB_KEY } from './constants'; -import { OnPubSubEvent } from '@n8n/decorators'; + +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; +import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee'; +import { JwtService } from '@/services/jwt.service'; +import { UrlService } from '@/services/url.service'; const DEFAULT_OIDC_CONFIG: OidcConfigDto = { clientId: '', @@ -422,7 +423,7 @@ export class OidcService { newConfig.clientSecret = this.oidcConfig.clientSecret; } try { - const discoveredMetadata = await client.discovery( + const discoveredMetadata = await this.createProxyAwareConfiguration( discoveryEndpoint, newConfig.clientId, newConfig.clientSecret, @@ -484,6 +485,46 @@ export class OidcService { } & OidcRuntimeConfig) | undefined; + /** + * Creates a proxy-aware configuration for openid-client. + * This method configures customFetch to respect HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables. + */ + private async createProxyAwareConfiguration( + discoveryUrl: URL, + clientId: string, + clientSecret: string, + ): Promise { + const configuration = await client.discovery(discoveryUrl, clientId, clientSecret); + + // Check if proxy environment variables are set + const hasProxyConfig = + process.env.HTTP_PROXY ?? process.env.HTTPS_PROXY ?? process.env.ALL_PROXY; + + if (hasProxyConfig) { + this.logger.debug('Configuring OIDC client with proxy support', { + HTTP_PROXY: process.env.HTTP_PROXY, + HTTPS_PROXY: process.env.HTTPS_PROXY, + NO_PROXY: process.env.NO_PROXY, + ALL_PROXY: process.env.ALL_PROXY, + }); + + // Create a proxy agent that automatically reads from environment variables + const proxyAgent = new EnvHttpProxyAgent(); + + // Configure customFetch to use the proxy agent + configuration[client.customFetch] = async (...args) => { + const [url, options] = args; + return await fetch(url, { + ...options, + // @ts-expect-error - dispatcher is an undici-specific option not in standard fetch + dispatcher: proxyAgent, + }); + }; + } + + return configuration; + } + private async getOidcConfiguration(): Promise { const now = Date.now(); if ( @@ -496,7 +537,7 @@ export class OidcService { ) { this.cachedOidcConfiguration = { ...this.oidcConfig, - configuration: client.discovery( + configuration: this.createProxyAwareConfiguration( this.oidcConfig.discoveryEndpoint, this.oidcConfig.clientId, this.oidcConfig.clientSecret, diff --git a/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts b/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts index d3ab865b8dd..0de1828d642 100644 --- a/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts +++ b/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts @@ -2,15 +2,21 @@ import type { SamlPreferences } from '@n8n/api-types'; import { mockInstance, mockLogger } from '@n8n/backend-test-utils'; import type { GlobalConfig } from '@n8n/config'; import { SettingsRepository } from '@n8n/db'; -import type { UserRepository } from '@n8n/db'; -import type { Settings } from '@n8n/db'; +import type { UserRepository, Settings } from '@n8n/db'; import { Container } from '@n8n/di'; import axios from 'axios'; import type express from 'express'; +import type { HttpProxyAgent } from 'http-proxy-agent'; +import type { HttpsProxyAgent } from 'https-proxy-agent'; import { mock } from 'jest-mock-extended'; import type { InstanceSettings } from 'n8n-core'; import type { IdentityProviderInstance, ServiceProviderInstance } from 'samlify'; +import { SAML_PREFERENCES_DB_KEY } from '../constants'; +import { InvalidSamlMetadataUrlError } from '../errors/invalid-saml-metadata-url.error'; +import { InvalidSamlMetadataError } from '../errors/invalid-saml-metadata.error'; +import { SamlValidator } from '../saml-validator'; + import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { Publisher } from '@/scaling/pubsub/publisher.service'; import type { UrlService } from '@/services/url.service'; @@ -18,11 +24,6 @@ import * as samlHelpers from '@/sso.ee/saml/saml-helpers'; import { SamlService } from '@/sso.ee/saml/saml.service.ee'; import * as ssoHelpers from '@/sso.ee/sso-helpers'; -import { SAML_PREFERENCES_DB_KEY } from '../constants'; -import { InvalidSamlMetadataUrlError } from '../errors/invalid-saml-metadata-url.error'; -import { InvalidSamlMetadataError } from '../errors/invalid-saml-metadata.error'; -import { SamlValidator } from '../saml-validator'; - jest.mock('axios'); const mockedAxios = axios as jest.Mocked; @@ -649,4 +650,202 @@ describe('SamlService', () => { expect(settingsRepository.delete).toHaveBeenCalledWith({ key: SAML_PREFERENCES_DB_KEY }); }); }); + + describe('proxy configuration', () => { + const originalEnv = process.env; + const validMetadataXml = + '\n\n \n \n \n \n MIIC4jCCAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJV\nSzEPMA0GA1UECgwGQm94eUhRMRIwEAYDVQQDDAlNb2NrIFNBTUwwIBcNMjIwMjI4\nMjE0NjM4WhgPMzAyMTA3MDEyMTQ2MzhaMDIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQK\nDAZCb3h5SFExEjAQBgNVBAMMCU1vY2sgU0FNTDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALGfYettMsct1T6tVUwTudNJH5Pnb9GGnkXi9Zw/e6x45DD0\nRuRONbFlJ2T4RjAE/uG+AjXxXQ8o2SZfb9+GgmCHuTJFNgHoZ1nFVXCmb/Hg8Hpd\n4vOAGXndixaReOiq3EH5XvpMjMkJ3+8+9VYMzMZOjkgQtAqO36eAFFfNKX7dTj3V\npwLkvz6/KFCq8OAwY+AUi4eZm5J57D31GzjHwfjH9WTeX0MyndmnNB1qV75qQR3b\n2/W5sGHRv+9AarggJkF+ptUkXoLtVA51wcfYm6hILptpde5FQC8RWY1YrswBWAEZ\nNfyrR4JeSweElNHg4NVOs4TwGjOPwWGqzTfgTlECAwEAATANBgkqhkiG9w0BAQsF\nAAOCAQEAAYRlYflSXAWoZpFfwNiCQVE5d9zZ0DPzNdWhAybXcTyMf0z5mDf6FWBW\n5Gyoi9u3EMEDnzLcJNkwJAAc39Apa4I2/tml+Jy29dk8bTyX6m93ngmCgdLh5Za4\nkhuU3AM3L63g7VexCuO7kwkjh/+LqdcIXsVGO6XDfu2QOs1Xpe9zIzLpwm/RNYeX\nUjbSj5ce/jekpAw7qyVVL4xOyh8AtUW1ek3wIw1MJvEgEPt0d16oshWJpoS1OT8L\nr/22SvYEo3EmSGdTVGgk3x3s+A0qWAqTcyjr7Q4s/GKYRFfomGwz0TZ4Iw1ZN99M\nm0eo2USlSRTVl7QHRTuiuSThHpLKQQ==\n \n \n \n urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n \n \n \n'; + + beforeEach(() => { + // Reset environment before each test + process.env = { ...originalEnv }; + jest.restoreAllMocks(); + jest.spyOn(samlService, 'loadSamlify').mockResolvedValue(undefined); + }); + + afterEach(() => { + // Restore original environment after each test + process.env = originalEnv; + }); + + test('should use proxy when HTTP_PROXY environment variable is set for HTTP URLs', async () => { + // Set HTTP proxy environment variable + const proxyUrl = 'http://proxy.example.com:8080'; + process.env.HTTP_PROXY = proxyUrl; + + // Use an HTTP metadata URL + const metadataUrl = 'http://saml.example.com/metadata'; + + // Mock the preferences to include a metadataUrl + type SamlServicePrivate = { _samlPreferences: SamlPreferences }; + (samlService as unknown as SamlServicePrivate)._samlPreferences = { + metadata: mockSamlConfig.metadata, + metadataUrl, + } as SamlPreferences; + + // Mock axios response + mockedAxios.get.mockResolvedValue({ + status: 200, + data: validMetadataXml, + }); + + // Mock validator + jest.spyOn(samlService['validator'], 'validateMetadata').mockResolvedValue(true); + + const result = await samlService.fetchMetadataFromUrl(); + + expect(result).toBe(validMetadataXml); + + // Verify that axios.get was called with the correct URL and agents + expect(mockedAxios.get).toHaveBeenCalledWith( + metadataUrl, + expect.objectContaining({ + httpAgent: expect.any(Object), + httpsAgent: expect.any(Object), + }), + ); + + // Get the actual call arguments to verify the agents + const callArgs = mockedAxios.get.mock.calls[0]; + if (!callArgs?.[1]) { + throw new Error('Expected axios.get to be called with arguments'); + } + + const { httpAgent, httpsAgent } = callArgs[1]; + + // Verify that both agents are created + expect(httpAgent).toBeDefined(); + expect(httpsAgent).toBeDefined(); + + // Verify the httpAgent has proxy configuration + expect(httpAgent).toHaveProperty('proxy'); + const httpProxyAgent = httpAgent as unknown as HttpProxyAgent; + expect(httpProxyAgent.proxy).toBeDefined(); + expect(httpProxyAgent.proxy.href).toBe(`${proxyUrl}/`); + + // The httpsAgent should also have the proxy (both are created with same proxy) + expect(httpsAgent).toHaveProperty('proxy'); + const httpsProxyAgent = httpsAgent as unknown as HttpsProxyAgent; + expect(httpsProxyAgent.proxy.href).toBe(`${proxyUrl}/`); + }); + + test('should use proxy when HTTPS_PROXY environment variable is set for HTTPS URLs', async () => { + // Set HTTPS proxy environment variable + const proxyUrl = 'https://proxy.example.com:8080'; + process.env.HTTPS_PROXY = proxyUrl; + + // Use an HTTPS metadata URL + const metadataUrl = 'https://saml.example.com/metadata'; + + // Mock the preferences to include a metadataUrl + type SamlServicePrivate = { _samlPreferences: SamlPreferences }; + (samlService as unknown as SamlServicePrivate)._samlPreferences = { + metadata: mockSamlConfig.metadata, + metadataUrl, + ignoreSSL: true, + } as SamlPreferences; + + // Mock axios response + mockedAxios.get.mockResolvedValue({ + status: 200, + data: validMetadataXml, + }); + + // Mock validator + jest.spyOn(samlService['validator'], 'validateMetadata').mockResolvedValue(true); + + const result = await samlService.fetchMetadataFromUrl(); + + expect(result).toBe(validMetadataXml); + + // Verify that axios.get was called with the correct URL and agents + expect(mockedAxios.get).toHaveBeenCalledWith( + metadataUrl, + expect.objectContaining({ + httpAgent: expect.any(Object), + httpsAgent: expect.any(Object), + }), + ); + + // Get the actual call arguments to verify the agents + const callArgs = mockedAxios.get.mock.calls[0]; + if (!callArgs?.[1]) { + throw new Error('Expected axios.get to be called with arguments'); + } + + const { httpAgent, httpsAgent } = callArgs[1]; + + // Verify that both agents are created + expect(httpAgent).toBeDefined(); + expect(httpsAgent).toBeDefined(); + + // Verify the httpsAgent has proxy configuration + expect(httpsAgent).toHaveProperty('proxy'); + const httpsProxyAgent = httpsAgent as unknown as HttpsProxyAgent; + expect(httpsProxyAgent.proxy).toBeDefined(); + expect(httpsProxyAgent.proxy.href).toBe(`${proxyUrl}/`); + + // Verify that the httpsAgent has the correct rejectUnauthorized setting when ignoreSSL is true + // HttpsProxyAgent stores connection options in connectOpts, not options + const httpsAgentWithConnectOpts = httpsProxyAgent as unknown as { + connectOpts?: { rejectUnauthorized?: boolean }; + }; + expect(httpsAgentWithConnectOpts.connectOpts?.rejectUnauthorized).toBe(false); + + // The httpAgent should also have the proxy (both are created with same proxy) + expect(httpAgent).toHaveProperty('proxy'); + const httpProxyAgent = httpAgent as unknown as HttpProxyAgent; + expect(httpProxyAgent.proxy.href).toBe(`${proxyUrl}/`); + }); + + test('should work without proxy when no proxy environment variables are set', async () => { + // Ensure no proxy env vars are set + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.ALL_PROXY; + + // Mock the preferences to include a metadataUrl + type SamlServicePrivate = { _samlPreferences: SamlPreferences }; + (samlService as unknown as SamlServicePrivate)._samlPreferences = { + metadata: mockSamlConfig.metadata, + metadataUrl: 'https://saml.example.com/metadata', + ignoreSSL: false, + } as SamlPreferences; + + // Mock axios response + mockedAxios.get.mockResolvedValue({ + status: 200, + data: validMetadataXml, + }); + + // Mock validator + jest.spyOn(samlService['validator'], 'validateMetadata').mockResolvedValue(true); + + const result = await samlService.fetchMetadataFromUrl(); + + expect(result).toBe(validMetadataXml); + + // Verify that axios.get was called with agents + expect(mockedAxios.get).toHaveBeenCalledWith( + 'https://saml.example.com/metadata', + expect.objectContaining({ + httpAgent: expect.any(Object), + httpsAgent: expect.any(Object), + }), + ); + + // Get the actual call arguments to verify the agents are NOT proxy agents + const callArgs = mockedAxios.get.mock.calls[0]; + if (!callArgs?.[1]) { + throw new Error('Expected axios.get to be called with arguments'); + } + + const { httpAgent, httpsAgent } = callArgs[1]; + + // When no proxy is configured, regular http/https Agents are created + // These should NOT have a proxy property + expect(httpAgent).not.toHaveProperty('proxy'); + expect(httpsAgent).not.toHaveProperty('proxy'); + }); + }); }); diff --git a/packages/cli/src/sso.ee/saml/saml.service.ee.ts b/packages/cli/src/sso.ee/saml/saml.service.ee.ts index 08724f2c7fc..9ce94093454 100644 --- a/packages/cli/src/sso.ee/saml/saml.service.ee.ts +++ b/packages/cli/src/sso.ee/saml/saml.service.ee.ts @@ -7,16 +7,11 @@ import { OnPubSubEvent } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import axios from 'axios'; import type express from 'express'; -import https from 'https'; -import { InstanceSettings } from 'n8n-core'; +import { createHttpProxyAgent, createHttpsProxyAgent, InstanceSettings } from 'n8n-core'; import { jsonParse, UnexpectedError } from 'n8n-workflow'; import { type IdentityProviderInstance, type ServiceProviderInstance } from 'samlify'; import type { BindingContext, PostBindingContext } from 'samlify/types/src/entity'; -import { AuthError } from '@/errors/response-errors/auth.error'; -import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -import { UrlService } from '@/services/url.service'; - import { SAML_PREFERENCES_DB_KEY } from './constants'; import { InvalidSamlMetadataUrlError } from './errors/invalid-saml-metadata-url.error'; import { InvalidSamlMetadataError } from './errors/invalid-saml-metadata.error'; @@ -34,7 +29,11 @@ import { SamlValidator } from './saml-validator'; import { getServiceProviderInstance } from './service-provider.ee'; import type { SamlLoginBinding, SamlUserAttributes } from './types'; import { isSsoJustInTimeProvisioningEnabled, reloadAuthenticationMethod } from '../sso-helpers'; + +import { AuthError } from '@/errors/response-errors/auth.error'; +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { PROVISIONING_PREFERENCES_DB_KEY } from '@/modules/provisioning.ee/constants'; +import { UrlService } from '@/services/url.service'; @Service() export class SamlService { @@ -444,11 +443,21 @@ export class SamlService { if (!this._samlPreferences.metadataUrl) throw new BadRequestError('Error fetching SAML Metadata, no Metadata URL set'); try { - // TODO:SAML: this will not work once axios is upgraded to > 1.2.0 (see checkServerIdentity) - const agent = new https.Agent({ - rejectUnauthorized: !this._samlPreferences.ignoreSSL, + // Create a proxy-aware HTTPS agent that respects HTTP_PROXY, HTTPS_PROXY, and NO_PROXY + // environment variables while also supporting SSL certificate validation options + const httpsAgent = createHttpsProxyAgent( + null, // Uses proxy from environment variables + this._samlPreferences.metadataUrl, + { + rejectUnauthorized: !this._samlPreferences.ignoreSSL, + }, + ); + const httpAgent = createHttpProxyAgent(null, this._samlPreferences.metadataUrl); + + const response = await axios.get(this._samlPreferences.metadataUrl, { + httpsAgent, + httpAgent, }); - const response = await axios.get(this._samlPreferences.metadataUrl, { httpsAgent: agent }); if (response.status === 200 && response.data) { const xml = (await response.data) as string; const validationResult = await this.validator.validateMetadata(xml); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77eb964475c..71a27c8e229 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -539,7 +539,7 @@ importers: version: 4.0.7 axios: specifier: 1.12.0 - version: 1.12.0(debug@4.4.1) + version: 1.12.0(debug@4.4.3) dotenv: specifier: 8.6.0 version: 8.6.0 @@ -564,7 +564,7 @@ importers: dependencies: axios: specifier: 1.12.0 - version: 1.12.0(debug@4.4.1) + version: 1.12.0(debug@4.4.3) devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -1052,7 +1052,7 @@ importers: version: 4.3.0 '@getzep/zep-cloud': specifier: 1.0.12 - version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621)) + version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d)) '@getzep/zep-js': specifier: 0.9.0 version: 0.9.0 @@ -1079,7 +1079,7 @@ importers: version: 0.3.4(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13) '@langchain/community': specifier: 'catalog:' - version: 0.3.50(a14f1c0904e0abadfde97956245c3242) + version: 0.3.50(a75c8af281af8c64873764a9c6c64007) '@langchain/core': specifier: 'catalog:' version: 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)) @@ -1202,7 +1202,7 @@ importers: version: 23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) langchain: specifier: 0.3.33 - version: 0.3.33(ca377405f102dc2905a39d54ecb4d621) + version: 0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d) lodash: specifier: 'catalog:' version: 4.17.21 @@ -1318,7 +1318,7 @@ importers: version: link:../eslint-plugin-community-nodes axios: specifier: 1.12.0 - version: 1.12.0(debug@4.4.1) + version: 1.12.0(debug@4.4.3) eslint: specifier: 'catalog:' version: 9.29.0(jiti@2.6.1) @@ -1532,7 +1532,7 @@ importers: version: 1.11.0 axios: specifier: 1.12.0 - version: 1.12.0(debug@4.4.1) + version: 1.12.0(debug@4.4.3) bcryptjs: specifier: 2.4.3 version: 2.4.3 @@ -1722,6 +1722,9 @@ importers: syslog-client: specifier: 1.1.1 version: 1.1.1 + undici: + specifier: ^7.16.0 + version: 7.16.0 uuid: specifier: 'catalog:' version: 10.0.0 @@ -1881,7 +1884,7 @@ importers: version: 9.42.1 axios: specifier: 1.12.0 - version: 1.12.0(debug@4.4.1) + version: 1.12.0(debug@4.4.3) callsites: specifier: 'catalog:' version: 3.1.0 @@ -2351,7 +2354,7 @@ importers: version: link:../../../@n8n/utils axios: specifier: 1.12.0 - version: 1.12.0(debug@4.4.1) + version: 1.12.0(debug@4.4.3) flatted: specifier: 'catalog:' version: 3.2.7 @@ -2608,7 +2611,7 @@ importers: version: 1.1.4 axios: specifier: 1.12.0 - version: 1.12.0(debug@4.4.1) + version: 1.12.0(debug@4.4.3) bowser: specifier: 2.11.0 version: 2.11.0 @@ -20366,7 +20369,7 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621))': + '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d))': dependencies: form-data: 4.0.4 node-fetch: 2.7.0(encoding@0.1.13) @@ -20375,7 +20378,7 @@ snapshots: zod: 3.25.67 optionalDependencies: '@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)) - langchain: 0.3.33(ca377405f102dc2905a39d54ecb4d621) + langchain: 0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d) transitivePeerDependencies: - encoding @@ -21067,7 +21070,7 @@ snapshots: - aws-crt - encoding - '@langchain/community@0.3.50(a14f1c0904e0abadfde97956245c3242)': + '@langchain/community@0.3.50(a75c8af281af8c64873764a9c6c64007)': dependencies: '@browserbasehq/stagehand': 1.9.0(@playwright/test@1.56.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@16.6.1)(encoding@0.1.13)(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(utf-8-validate@5.0.10)(zod@3.25.67) '@ibm-cloud/watsonx-ai': 1.1.2 @@ -21079,7 +21082,7 @@ snapshots: flat: 5.0.2 ibm-cloud-sdk-core: 5.3.2 js-yaml: 4.1.0 - langchain: 0.3.33(ca377405f102dc2905a39d54ecb4d621) + langchain: 0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d) langsmith: 0.3.55(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)) openai: 5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) uuid: 10.0.0 @@ -21093,7 +21096,7 @@ snapshots: '@aws-sdk/credential-provider-node': 3.808.0 '@azure/storage-blob': 12.26.0 '@browserbasehq/sdk': 2.6.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621)) + '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d)) '@getzep/zep-js': 0.9.0 '@google-ai/generativelanguage': 3.4.0(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13) @@ -22353,7 +22356,7 @@ snapshots: '@rudderstack/rudder-sdk-node@2.1.4(tslib@2.8.1)': dependencies: - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) axios-retry: 4.5.0(axios@1.12.0) component-type: 2.0.0 join-component: 1.1.0 @@ -24937,7 +24940,7 @@ snapshots: axios-retry@4.5.0(axios@1.12.0): dependencies: - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) is-retry-allowed: 2.2.0 axios@1.12.0(debug@4.3.6): @@ -24956,6 +24959,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.12.0(debug@4.4.3): + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + b4a@1.6.7: {} babel-jest@29.6.2(@babel/core@7.26.10): @@ -25331,7 +25342,7 @@ snapshots: bundlemon@3.1.0(typescript@5.9.2): dependencies: - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) axios-retry: 4.5.0(axios@1.12.0) brotli-size: 4.0.0 bundlemon-utils: 2.0.1 @@ -27717,6 +27728,10 @@ snapshots: optionalDependencies: debug: 4.4.1(supports-color@8.1.1) + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -28441,7 +28456,7 @@ snapshots: '@types/debug': 4.1.12 '@types/node': 20.19.21 '@types/tough-cookie': 4.0.5 - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) camelcase: 6.3.0 debug: 4.4.3 dotenv: 16.6.1 @@ -28451,7 +28466,7 @@ snapshots: isstream: 0.1.2 jsonwebtoken: 9.0.2 mime-types: 2.1.35 - retry-axios: 2.6.0(axios@1.12.0(debug@4.4.3)) + retry-axios: 2.6.0(axios@1.12.0) tough-cookie: 4.1.4 transitivePeerDependencies: - supports-color @@ -28529,7 +28544,7 @@ snapshots: infisical-node@1.3.0: dependencies: - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) dotenv: 16.3.1 tweetnacl: 1.0.3 tweetnacl-util: 0.15.1 @@ -29907,7 +29922,7 @@ snapshots: kuler@2.0.0: {} - langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621): + langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d): dependencies: '@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)) '@langchain/openai': 0.6.16(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)) @@ -29930,7 +29945,7 @@ snapshots: '@langchain/groq': 0.2.3(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13) '@langchain/mistralai': 0.2.3(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))) '@langchain/ollama': 0.2.3(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))) - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) cheerio: 1.0.0 handlebars: 4.7.8 transitivePeerDependencies: @@ -32074,7 +32089,7 @@ snapshots: posthog-node@3.2.1: dependencies: - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) rusha: 0.8.14 transitivePeerDependencies: - debug @@ -32779,9 +32794,9 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - retry-axios@2.6.0(axios@1.12.0(debug@4.4.3)): + retry-axios@2.6.0(axios@1.12.0): dependencies: - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) retry-request@7.0.2(encoding@0.1.13): dependencies: @@ -33388,7 +33403,7 @@ snapshots: asn1.js: 5.4.1 asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1) asn1.js-rfc5280: 3.0.0 - axios: 1.12.0(debug@4.4.1) + axios: 1.12.0(debug@4.4.3) big-integer: 1.6.52 bignumber.js: 9.1.2 binascii: 0.0.2 @@ -34914,7 +34929,7 @@ snapshots: vite-node@3.1.3(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.89.2)(terser@5.16.1)(tsx@4.19.3): dependencies: cac: 6.7.14 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.3.5(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.89.2)(terser@5.16.1)(tsx@4.19.3)