From 7a88a8d9cf5c2b5e4829252682648e83d06aab27 Mon Sep 17 00:00:00 2001 From: roberthicks Date: Wed, 11 Dec 2024 12:23:36 -0500 Subject: [PATCH 1/2] feat: Add oauth support --- src/lib/request/request.ts | 21 ++++++++++-- src/lib/request/requestManager.ts | 55 +++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/lib/request/request.ts b/src/lib/request/request.ts index c80f7e9..236747a 100644 --- a/src/lib/request/request.ts +++ b/src/lib/request/request.ts @@ -42,6 +42,7 @@ const getTopParentModuleName = (parent: NodeModule | null): string => { const makeSnykRequest = async ( request: SnykRequest, snykToken = '', + oauthBearerToken = '', apiUrl = DEFAULT_API, apiUrlREST = DEFAULT_REST_API, userAgentPrefix = '', @@ -57,12 +58,20 @@ const makeSnykRequest = async ( userAgentPrefix != '' && !userAgentPrefix.endsWith('/') ? userAgentPrefix + '/' : userAgentPrefix; + + // prioritize snykToken but use oauthBearerToken if snykToken isn't provided + const authorizationToken = snykToken + ? `token ${snykToken}` + : oauthBearerToken + ? `Bearer ${oauthBearerToken}` + : ''; + const requestHeaders: Record = { 'Content-Type': request.useRESTApi && request.body ? 'application/vnd.api+json' : 'application/json', - Authorization: 'token ' + snykToken, + Authorization: authorizationToken, 'User-Agent': `${topParentModuleName}${userAgentPrefixChecked}tech-services/snyk-request-manager/1.0`, }; let apiClient; @@ -70,7 +79,10 @@ const makeSnykRequest = async ( apiClient = axios.create({ baseURL: request.useRESTApi ? apiUrlREST : apiUrl, responseType: 'json', - headers: { ...requestHeaders, ...request.headers }, + headers: { + ...requestHeaders, + ...request.headers, + }, transitional: { clarifyTimeoutError: true, }, @@ -81,7 +93,10 @@ const makeSnykRequest = async ( apiClient = axios.create({ baseURL: request.useRESTApi ? apiUrlREST : apiUrl, responseType: 'json', - headers: { ...requestHeaders, ...request.headers }, + headers: { + ...requestHeaders, + ...request.headers, + }, transitional: { clarifyTimeoutError: true, }, diff --git a/src/lib/request/requestManager.ts b/src/lib/request/requestManager.ts index 0f7aa15..5446b8e 100644 --- a/src/lib/request/requestManager.ts +++ b/src/lib/request/requestManager.ts @@ -46,6 +46,11 @@ function getRESTAPI(endpoint: string): string { return new URL(`${apiData.protocol}//${apiData.host}/rest`).toString(); } +function getOauthToken(): string { + const oauthToken: string = process.env.OAUTH_BEARER_TOKEN || ''; + return oauthToken; +} + const getConfig = (): { endpoint: string; token: string } => { const snykApiEndpoint: string = process.env.SNYK_API || @@ -69,6 +74,7 @@ class RequestsManager { _retryCounter: Map; _MAX_RETRY_COUNT: number; _snykToken: string; + _oauthBearerToken?: string; // Optional OAuth token _userAgentPrefix?: string; //snykToken = '', burstSize = 10, period = 500, maxRetryCount = 5 @@ -82,6 +88,7 @@ class RequestsManager { this._events = {}; this._retryCounter = new Map(); this._MAX_RETRY_COUNT = params?.maxRetryCount || 5; + this._oauthBearerToken = getOauthToken(); this._snykToken = params?.snykToken ?? this._userConfig.token; this._apiUrl = this._userConfig.endpoint; this._apiUrlREST = getRESTAPI(this._userConfig.endpoint); @@ -101,19 +108,41 @@ class RequestsManager { _makeRequest = async (request: QueuedRequest): Promise => { const requestId = request.id; try { - const response = await makeSnykRequest( - request.snykRequest, - this._snykToken, - this._apiUrl, - this._apiUrlREST, - this._userAgentPrefix, - ); - this._emit({ - eventType: eventType.data, - channel: request.channel, - requestId, - data: response, - }); + // Pass oauthBearerToken if available + if ( + this._oauthBearerToken != null && + this._oauthBearerToken.trim() != '' + ) { + const response = await makeSnykRequest( + request.snykRequest, + '', + this._oauthBearerToken, + this._apiUrl, + this._apiUrlREST, + this._userAgentPrefix, + ); + this._emit({ + eventType: eventType.data, + channel: request.channel, + requestId, + data: response, + }); + } else { + const response = await makeSnykRequest( + request.snykRequest, + this._snykToken, + '', + this._apiUrl, + this._apiUrlREST, + this._userAgentPrefix, + ); + this._emit({ + eventType: eventType.data, + channel: request.channel, + requestId, + data: response, + }); + } } catch (err) { const overloadedError = requestsManagerError.requestsManagerErrorOverload( err, From 3858dd5b4ac0364fe686a15348dcb4ad4bc979b2 Mon Sep 17 00:00:00 2001 From: roberthicks Date: Thu, 12 Dec 2024 14:19:13 -0500 Subject: [PATCH 2/2] chore: Add test classes for oauth token and changed oauth environment variable to SNYK_OAUTH_TOKEN --- src/lib/request/requestManager.ts | 2 +- test/lib/request/request.test.ts | 82 +++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/lib/request/requestManager.ts b/src/lib/request/requestManager.ts index 5446b8e..fc56a7b 100644 --- a/src/lib/request/requestManager.ts +++ b/src/lib/request/requestManager.ts @@ -47,7 +47,7 @@ function getRESTAPI(endpoint: string): string { } function getOauthToken(): string { - const oauthToken: string = process.env.OAUTH_BEARER_TOKEN || ''; + const oauthToken: string = process.env.SNYK_OAUTH_TOKEN || ''; return oauthToken; } diff --git a/test/lib/request/request.test.ts b/test/lib/request/request.test.ts index 20339d5..fa5c479 100644 --- a/test/lib/request/request.test.ts +++ b/test/lib/request/request.test.ts @@ -229,3 +229,85 @@ describe('Test Snyk Utils error handling/classification', () => { } }); }); + +describe('Test makeSnykRequest with oauthBearerToken', () => { + beforeEach(() => { + nock.cleanAll(); + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it('should set Bearer token in Authorization header when oauthBearerToken is provided for DEFAULT_API', async () => { + const testToken = 'test-oauth-token'; + const request = { + verb: 'GET', + url: '/test-endpoint', + }; + + const scope = nock('https://api.snyk.io/v1') + .get('/test-endpoint') + .matchHeader('Authorization', `Bearer ${testToken}`) + .reply(200, { success: true }); + + await makeSnykRequest(request, '', testToken); + + expect(scope.isDone()).toBe(true); + }); + + it('should set Bearer token in Authorization header when oauthBearerToken is provided for DEFAULT_REST_API', async () => { + const testToken = 'test-oauth-token'; + const request = { + verb: 'GET', + url: '/test-endpoint', + useRESTApi: true, + }; + + const scope = nock('https://api.snyk.io/rest/') + .get('/test-endpoint') + .matchHeader('Authorization', `Bearer ${testToken}`) + .reply(200, { success: true }); + + await makeSnykRequest(request, '', testToken); + + expect(scope.isDone()).toBe(true); + }); + + it('should prioritize snykToken over oauthBearerToken when both are provided for DEFAULT_API', async () => { + const snykToken = 'test-snyk-token'; + const oauthToken = 'test-oauth-token'; + const request = { + verb: 'GET', + url: '/test-endpoint', + }; + + const scope = nock('https://api.snyk.io/v1') + .get('/test-endpoint') + .matchHeader('Authorization', `token ${snykToken}`) + .reply(200, { success: true }); + + await makeSnykRequest(request, snykToken, oauthToken); + + expect(scope.isDone()).toBe(true); + }); + + it('should prioritize snykToken over oauthBearerToken when both are provided for DEFAULT_REST_API', async () => { + const snykToken = 'test-snyk-token'; + const oauthToken = 'test-oauth-token'; + const request = { + verb: 'GET', + url: '/test-endpoint', + useRESTApi: true, + }; + + const scope = nock('https://api.snyk.io/rest/') + .get('/test-endpoint') + .matchHeader('Authorization', `token ${snykToken}`) + .reply(200, { success: true }); + + await makeSnykRequest(request, snykToken, oauthToken); + + expect(scope.isDone()).toBe(true); + }); +});