Skip to content

Commit bdb87b2

Browse files
authored
Merge pull request #1178 from JupiterOne/nick/proxy-support-alpha
Adds proxy support using the Alpha client
2 parents 2cbb6d4 + 087412f commit bdb87b2

File tree

3 files changed

+200
-1
lines changed

3 files changed

+200
-1
lines changed

packages/integration-sdk-entity-validator/src/__tests__/validator.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AnySchema } from 'ajv';
22
import { EntityValidator } from '../validator';
33

44
const RESOLVED_SCHEMAS_URL =
5-
'https://raw.githubusercontent.com/JupiterOne/data-model/main/external/resolvedSchemas.json';
5+
'https://api.us.jupiterone.io/data-model/schemas/classes';
66

77
const ENTITY_SCHEMA = {
88
$schema: 'http://json-schema.org/draft-07/schema#',

packages/integration-sdk-runtime/src/api/__tests__/index.test.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,171 @@ describe('real Alpha request with fake API key', () => {
171171
}
172172
});
173173
});
174+
175+
describe('createApiClient', () => {
176+
const originalEnv = process.env;
177+
178+
beforeEach(() => {
179+
jest.resetModules();
180+
process.env = { ...originalEnv };
181+
});
182+
183+
afterAll(() => {
184+
process.env = originalEnv;
185+
});
186+
187+
describe('proxy configuration', () => {
188+
it('should not configure proxy when no proxy URL is provided', () => {
189+
const client = createApiClient({
190+
apiBaseUrl: 'https://api.example.com',
191+
account: 'test-account',
192+
accessToken: 'test-token',
193+
});
194+
195+
// The client should be created without proxy configuration
196+
expect(client).toBeDefined();
197+
});
198+
199+
it('should configure proxy when proxyUrl parameter is provided', () => {
200+
const proxyUrl = 'https://foo:[email protected]:8888';
201+
202+
const client = createApiClient({
203+
apiBaseUrl: 'https://api.example.com',
204+
account: 'test-account',
205+
accessToken: 'test-token',
206+
proxyUrl,
207+
});
208+
209+
expect(client).toBeDefined();
210+
// Note: We can't easily test the internal proxy config without exposing it
211+
// This test verifies the client is created successfully with proxy config
212+
});
213+
214+
it('should configure proxy from HTTPS_PROXY environment variable', () => {
215+
process.env.HTTPS_PROXY = 'https://foo:[email protected]:8888';
216+
217+
const client = createApiClient({
218+
apiBaseUrl: 'https://api.example.com',
219+
account: 'test-account',
220+
accessToken: 'test-token',
221+
});
222+
223+
expect(client).toBeDefined();
224+
});
225+
226+
it('should configure proxy from https_proxy environment variable', () => {
227+
process.env.https_proxy = 'http://user:[email protected]:3128';
228+
229+
const client = createApiClient({
230+
apiBaseUrl: 'https://api.example.com',
231+
account: 'test-account',
232+
accessToken: 'test-token',
233+
});
234+
235+
expect(client).toBeDefined();
236+
});
237+
238+
it('should prefer HTTPS_PROXY over https_proxy', () => {
239+
process.env.HTTPS_PROXY = 'https://primary:[email protected]:8888';
240+
process.env.https_proxy = 'http://secondary:[email protected]:3128';
241+
242+
const client = createApiClient({
243+
apiBaseUrl: 'https://api.example.com',
244+
account: 'test-account',
245+
accessToken: 'test-token',
246+
});
247+
248+
expect(client).toBeDefined();
249+
});
250+
251+
it('should prefer proxyUrl parameter over environment variables', () => {
252+
process.env.HTTPS_PROXY = 'https://env:[email protected]:8888';
253+
const proxyUrl = 'https://param:[email protected]:9999';
254+
255+
const client = createApiClient({
256+
apiBaseUrl: 'https://api.example.com',
257+
account: 'test-account',
258+
accessToken: 'test-token',
259+
proxyUrl,
260+
});
261+
262+
expect(client).toBeDefined();
263+
});
264+
});
265+
266+
describe('parseProxyUrl functionality', () => {
267+
// We need to import the parseProxyUrl function or test it indirectly
268+
it('should handle proxy URL with authentication', () => {
269+
process.env.HTTPS_PROXY =
270+
'https://username:[email protected]:8888';
271+
272+
const client = createApiClient({
273+
apiBaseUrl: 'https://api.example.com',
274+
account: 'test-account',
275+
accessToken: 'test-token',
276+
});
277+
278+
expect(client).toBeDefined();
279+
});
280+
281+
it('should handle proxy URL without authentication', () => {
282+
process.env.HTTPS_PROXY = 'https://proxy.example.com:8888';
283+
284+
const client = createApiClient({
285+
apiBaseUrl: 'https://api.example.com',
286+
account: 'test-account',
287+
accessToken: 'test-token',
288+
});
289+
290+
expect(client).toBeDefined();
291+
});
292+
293+
it('should handle HTTP proxy URLs', () => {
294+
process.env.HTTPS_PROXY = 'http://proxy.example.com:3128';
295+
296+
const client = createApiClient({
297+
apiBaseUrl: 'https://api.example.com',
298+
account: 'test-account',
299+
accessToken: 'test-token',
300+
});
301+
302+
expect(client).toBeDefined();
303+
});
304+
305+
it('should throw an error for invalid proxy URLs', () => {
306+
process.env.HTTPS_PROXY = 'invalid-url';
307+
308+
expect(() => {
309+
createApiClient({
310+
apiBaseUrl: 'https://api.example.com',
311+
account: 'test-account',
312+
accessToken: 'test-token',
313+
});
314+
}).toThrow();
315+
});
316+
317+
it('should use default ports when not specified', () => {
318+
// Test HTTPS default port (443)
319+
process.env.HTTPS_PROXY = 'https://proxy.example.com';
320+
321+
let client = createApiClient({
322+
apiBaseUrl: 'https://api.example.com',
323+
account: 'test-account',
324+
accessToken: 'test-token',
325+
});
326+
327+
expect(client).toBeDefined();
328+
329+
// Test HTTP default port (80)
330+
process.env.HTTPS_PROXY = 'http://proxy.example.com';
331+
332+
client = createApiClient({
333+
apiBaseUrl: 'https://api.example.com',
334+
account: 'test-account',
335+
accessToken: 'test-token',
336+
});
337+
338+
expect(client).toBeDefined();
339+
});
340+
});
341+
});

packages/integration-sdk-runtime/src/api/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Alpha, AlphaInterceptor, AlphaOptions } from '@lifeomic/alpha';
2+
import { AxiosProxyConfig } from 'axios';
23
import { IntegrationError } from '@jupiterone/integration-sdk-core';
34
import dotenv from 'dotenv';
45
import dotenvExpand from 'dotenv-expand';
@@ -18,6 +19,7 @@ interface CreateApiClientInput {
1819
retryOptions?: RetryOptions;
1920
compressUploads?: boolean;
2021
alphaOptions?: AlphaOptions;
22+
proxyUrl?: string;
2123
}
2224

2325
interface RetryOptions {
@@ -43,6 +45,7 @@ export function createApiClient({
4345
retryOptions,
4446
compressUploads,
4547
alphaOptions,
48+
proxyUrl,
4649
}: CreateApiClientInput): ApiClient {
4750
const headers: Record<string, string> = {
4851
'JupiterOne-Account': account,
@@ -52,10 +55,15 @@ export function createApiClient({
5255
if (accessToken) {
5356
headers.Authorization = `Bearer ${accessToken}`;
5457
}
58+
59+
const proxyUrlString = proxyUrl || getProxyFromEnvironment();
60+
const proxy = proxyUrlString ? parseProxyUrl(proxyUrlString) : undefined;
61+
5562
const opts: AlphaOptions = {
5663
baseURL: apiBaseUrl,
5764
headers,
5865
retry: retryOptions ?? {},
66+
...(proxy && { proxy }),
5967
...alphaOptions,
6068
};
6169

@@ -161,3 +169,26 @@ export const getApiKeyFromEnvironment = () =>
161169

162170
export const getAccountFromEnvironment = () =>
163171
getFromEnv('JUPITERONE_ACCOUNT', IntegrationAccountRequiredError);
172+
173+
function parseProxyUrl(proxyUrl: string) {
174+
const url = new URL(proxyUrl);
175+
const proxy: AxiosProxyConfig = {
176+
host: url.hostname,
177+
port: parseInt(url.port) || (url.protocol === 'https:' ? 443 : 80),
178+
protocol: url.protocol.replace(':', ''),
179+
};
180+
181+
if (url.username && url.password) {
182+
proxy.auth = {
183+
username: decodeURIComponent(url.username),
184+
password: decodeURIComponent(url.password),
185+
};
186+
}
187+
188+
return proxy;
189+
}
190+
191+
function getProxyFromEnvironment(): string | undefined {
192+
dotenvExpand(dotenv.config());
193+
return process.env.HTTPS_PROXY || process.env.https_proxy;
194+
}

0 commit comments

Comments
 (0)