Skip to content

Commit afdd691

Browse files
authored
🔧 fix: Axios Proxy Usage And Bump mongoose (danny-avila#6298)
* fix: bump mongoose to fix nested schema errors * fix: Enhance Axios instance creation with improved proxy handling and error logging * fix: Refactor Axios instance creation and remove proxy handling from file upload functions * fix: Update proxy configuration in Axios instance creation and add unit tests
1 parent 95b4934 commit afdd691

File tree

6 files changed

+278
-101
lines changed

6 files changed

+278
-101
lines changed

‎api/config/index.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,37 @@ const sendEvent = (res, event) => {
4848
res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`);
4949
};
5050

51+
/**
52+
* Creates and configures an Axios instance with optional proxy settings.
53+
*
54+
* @typedef {import('axios').AxiosInstance} AxiosInstance
55+
* @typedef {import('axios').AxiosProxyConfig} AxiosProxyConfig
56+
*
57+
* @returns {AxiosInstance} A configured Axios instance
58+
* @throws {Error} If there's an issue creating the Axios instance or parsing the proxy URL
59+
*/
5160
function createAxiosInstance() {
5261
const instance = axios.create();
5362

5463
if (process.env.proxy) {
55-
const url = new URL(process.env.proxy);
56-
instance.defaults.proxy = {
57-
host: url.hostname,
58-
protocol: url.protocol.replace(':', ''),
59-
};
64+
try {
65+
const url = new URL(process.env.proxy);
66+
67+
/** @type {AxiosProxyConfig} */
68+
const proxyConfig = {
69+
host: url.hostname.replace(/^\[|\]$/g, ''),
70+
protocol: url.protocol.replace(':', ''),
71+
};
72+
73+
if (url.port) {
74+
proxyConfig.port = parseInt(url.port, 10);
75+
}
76+
77+
instance.defaults.proxy = proxyConfig;
78+
} catch (error) {
79+
console.error('Error parsing proxy URL:', error);
80+
throw new Error(`Invalid proxy URL: ${process.env.proxy}`);
81+
}
6082
}
6183

6284
return instance;

‎api/config/index.spec.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
const axios = require('axios');
2+
const { createAxiosInstance } = require('./index');
3+
4+
// Mock axios
5+
jest.mock('axios', () => ({
6+
interceptors: {
7+
request: { use: jest.fn(), eject: jest.fn() },
8+
response: { use: jest.fn(), eject: jest.fn() },
9+
},
10+
create: jest.fn().mockReturnValue({
11+
defaults: {
12+
proxy: null,
13+
},
14+
get: jest.fn().mockResolvedValue({ data: {} }),
15+
post: jest.fn().mockResolvedValue({ data: {} }),
16+
put: jest.fn().mockResolvedValue({ data: {} }),
17+
delete: jest.fn().mockResolvedValue({ data: {} }),
18+
}),
19+
get: jest.fn().mockResolvedValue({ data: {} }),
20+
post: jest.fn().mockResolvedValue({ data: {} }),
21+
put: jest.fn().mockResolvedValue({ data: {} }),
22+
delete: jest.fn().mockResolvedValue({ data: {} }),
23+
reset: jest.fn().mockImplementation(function () {
24+
this.get.mockClear();
25+
this.post.mockClear();
26+
this.put.mockClear();
27+
this.delete.mockClear();
28+
this.create.mockClear();
29+
}),
30+
}));
31+
32+
describe('createAxiosInstance', () => {
33+
const originalEnv = process.env;
34+
35+
beforeEach(() => {
36+
// Reset mocks
37+
jest.clearAllMocks();
38+
// Create a clean copy of process.env
39+
process.env = { ...originalEnv };
40+
// Default: no proxy
41+
delete process.env.proxy;
42+
});
43+
44+
afterAll(() => {
45+
// Restore original process.env
46+
process.env = originalEnv;
47+
});
48+
49+
test('creates an axios instance without proxy when no proxy env is set', () => {
50+
const instance = createAxiosInstance();
51+
52+
expect(axios.create).toHaveBeenCalledTimes(1);
53+
expect(instance.defaults.proxy).toBeNull();
54+
});
55+
56+
test('configures proxy correctly with hostname and protocol', () => {
57+
process.env.proxy = 'http://example.com';
58+
59+
const instance = createAxiosInstance();
60+
61+
expect(axios.create).toHaveBeenCalledTimes(1);
62+
expect(instance.defaults.proxy).toEqual({
63+
host: 'example.com',
64+
protocol: 'http',
65+
});
66+
});
67+
68+
test('configures proxy correctly with hostname, protocol and port', () => {
69+
process.env.proxy = 'https://proxy.example.com:8080';
70+
71+
const instance = createAxiosInstance();
72+
73+
expect(axios.create).toHaveBeenCalledTimes(1);
74+
expect(instance.defaults.proxy).toEqual({
75+
host: 'proxy.example.com',
76+
protocol: 'https',
77+
port: 8080,
78+
});
79+
});
80+
81+
test('handles proxy URLs with authentication', () => {
82+
process.env.proxy = 'http://user:[email protected]:3128';
83+
84+
const instance = createAxiosInstance();
85+
86+
expect(axios.create).toHaveBeenCalledTimes(1);
87+
expect(instance.defaults.proxy).toEqual({
88+
host: 'proxy.example.com',
89+
protocol: 'http',
90+
port: 3128,
91+
// Note: The current implementation doesn't handle auth - if needed, add this functionality
92+
});
93+
});
94+
95+
test('throws error when proxy URL is invalid', () => {
96+
process.env.proxy = 'invalid-url';
97+
98+
expect(() => createAxiosInstance()).toThrow('Invalid proxy URL');
99+
expect(axios.create).toHaveBeenCalledTimes(1);
100+
});
101+
102+
// If you want to test the actual URL parsing more thoroughly
103+
test('handles edge case proxy URLs correctly', () => {
104+
// IPv6 address
105+
process.env.proxy = 'http://[::1]:8080';
106+
107+
let instance = createAxiosInstance();
108+
109+
expect(instance.defaults.proxy).toEqual({
110+
host: '::1',
111+
protocol: 'http',
112+
port: 8080,
113+
});
114+
115+
// URL with path (which should be ignored for proxy config)
116+
process.env.proxy = 'http://proxy.example.com:8080/some/path';
117+
118+
instance = createAxiosInstance();
119+
120+
expect(instance.defaults.proxy).toEqual({
121+
host: 'proxy.example.com',
122+
protocol: 'http',
123+
port: 8080,
124+
});
125+
});
126+
});

‎api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
"memorystore": "^1.6.7",
8383
"mime": "^3.0.0",
8484
"module-alias": "^2.2.3",
85-
"mongoose": "^8.9.5",
85+
"mongoose": "^8.12.1",
8686
"multer": "^1.4.5-lts.1",
8787
"nanoid": "^3.3.7",
8888
"nodemailer": "^6.9.15",

‎api/server/services/Files/Code/crud.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
const axios = require('axios');
21
const FormData = require('form-data');
32
const { getCodeBaseURL } = require('@librechat/agents');
3+
const { createAxiosInstance } = require('~/config');
44
const { logAxiosError } = require('~/utils');
55

6+
const axios = createAxiosInstance();
7+
68
const MAX_FILE_SIZE = 150 * 1024 * 1024;
79

810
/**
@@ -27,13 +29,6 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
2729
timeout: 15000,
2830
};
2931

30-
if (process.env.PROXY) {
31-
options.proxy = {
32-
host: process.env.PROXY,
33-
protocol: process.env.PROXY.startsWith('https') ? 'https' : 'http',
34-
};
35-
}
36-
3732
const response = await axios(options);
3833
return response;
3934
} catch (error) {
@@ -79,13 +74,6 @@ async function uploadCodeEnvFile({ req, stream, filename, apiKey, entity_id = ''
7974
maxBodyLength: MAX_FILE_SIZE,
8075
};
8176

82-
if (process.env.PROXY) {
83-
options.proxy = {
84-
host: process.env.PROXY,
85-
protocol: process.env.PROXY.startsWith('https') ? 'https' : 'http',
86-
};
87-
}
88-
8977
const response = await axios.post(`${baseURL}/upload`, form, options);
9078

9179
/** @type {{ message: string; session_id: string; files: Array<{ fileId: string; filename: string }> }} */

0 commit comments

Comments
 (0)