diff --git a/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts b/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts index aeaca7b4..f27c25f8 100644 --- a/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts +++ b/packages/authentication-adapters/src/compositeAuthenticationAdapter.ts @@ -142,8 +142,13 @@ function getHttpInterceptorsForAuths( matchingRequirements: Partial>, providerConfig: CompositeAuthProviderConfig ): Array> { - return Object.entries(matchingRequirements).map((entry) => - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (providerConfig as any)[entry[0]](entry[1]) + return Object.entries(matchingRequirements).map( + ([authProvider, authParam]) => { + if (providerConfig[authProvider] !== undefined) { + return providerConfig[authProvider](authParam); + } else { + return passThroughInterceptor; + } + } ); } diff --git a/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts b/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts index 7364dfb7..8559684d 100644 --- a/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts +++ b/packages/authentication-adapters/test/compositeAuthenticationAdapter.test.ts @@ -5,29 +5,207 @@ import { customQueryAuthenticationProvider } from '../src/customQueryAuthenticat import { customHeaderAuthenticationProvider } from '../src/customHeaderAuthenticationAdapter'; import { callHttpInterceptors } from '../../core/src/http/httpInterceptor'; import { HttpRequest, HttpResponse } from '@apimatic/core-interfaces'; +import { requestAuthenticationProvider } from '../../oauth-adapters/src/oauthAuthenticationAdapter'; +import { OAuthToken } from '../../oauth-adapters/src/oAuthToken'; -describe('test composite Authentication Adapter', () => { - const config = { +interface Configuration { + basicAuthCredentials?: { + username: string; + password: string; + }; + accessTokenCredentials?: { + accessToken: string; + }; + apiKeyCredentials?: { + token: string; + apiKey: string; + }; + apiHeaderCredentials?: { + token: string; + apiKey: string; + }; + oAuthBearerTokenCredentials?: { + accessToken: string; + }; + oAuthCCGCredentials?: { + oAuthClientId: string; + oAuthClientSecret: string; + oAuthToken?: OAuthToken; + oAuthScopes?: 'read' | 'write'; + }; + oAuthACGCredentials?: { + oAuthClientId: string; + oAuthClientSecret: string; + oAuthRedirectUri: string; + oAuthToken?: OAuthToken; + oAuthScopes?: 'read' | 'write'; + }; + timeout: number; + environment: 'Production' | 'Testing'; + customUrl: string; +} + +describe('test composite authentication adapter with false or empty security requirements', () => { + const config: Configuration = { + timeout: 60000, + environment: 'Production', + customUrl: 'https://connect.product.com', + }; + + const authConfig = { + apiKey: + config.apiKeyCredentials && + customQueryAuthenticationProvider(config.apiKeyCredentials), + apiHeader: + config.apiHeaderCredentials && + customHeaderAuthenticationProvider(config.apiHeaderCredentials), + }; + + const response: HttpResponse = { + statusCode: 200, + body: 'testBody', + headers: { 'test-header': 'test-value' }, + }; + + it('should test false security requirements', async () => { + const request: HttpRequest = { + method: 'GET', + url: 'http://apimatic.hopto.org:3000/test/requestBuilder', + }; + const securityRequirements = false; + const provider = compositeAuthenticationProvider(authConfig); + const interceptor = [provider(securityRequirements)]; + const client = async (req) => { + return { request: req, response }; + }; + const executor = callHttpInterceptors(interceptor, client); + const context = await executor(request, undefined); + expect(context.request.headers).toEqual(undefined); + expect(context.request.url).toEqual( + 'http://apimatic.hopto.org:3000/test/requestBuilder' + ); + }); + + it('should test empty security requirements', async () => { + try { + const request: HttpRequest = { + method: 'GET', + url: 'http://apimatic.hopto.org:3000/test/requestBuilder', + }; + const securityRequirements = []; + const provider = compositeAuthenticationProvider(authConfig); + const interceptor = [provider(securityRequirements)]; + const client = async (req) => { + return { request: req, response }; + }; + const executor = callHttpInterceptors(interceptor, client); + const context = await executor(request, undefined); + expect(context.request.headers).toEqual(undefined); + expect(context.request.auth).toEqual(undefined); + } catch (error) { + expect(error.message).toEqual( + 'Required authentication credentials for this API call are not provided or all provided auth combinations are disabled' + ); + } + }); +}); + +describe('test composite authentication adapter with no credentials object provided', () => { + const config: Configuration = { + timeout: 60000, + environment: 'Production', + customUrl: 'https://connect.product.com', + }; + + const authConfig = { + apiKey: + config.apiKeyCredentials && + customQueryAuthenticationProvider(config.apiKeyCredentials), + apiHeader: + config.apiHeaderCredentials && + customHeaderAuthenticationProvider(config.apiHeaderCredentials), + }; + + const response: HttpResponse = { + statusCode: 200, + body: 'testBody', + headers: { 'test-header': 'test-value' }, + }; + + it('should test OR scheme with enabled custom query and enabled custom header', async () => { + const request: HttpRequest = { + method: 'GET', + url: 'http://apimatic.hopto.org:3000/test/requestBuilder', + }; + const securityRequirements = [{ apiKey: true }, { apiHeader: true }]; + const provider = compositeAuthenticationProvider(authConfig); + const interceptor = [provider(securityRequirements)]; + const client = async (req) => { + return { request: req, response }; + }; + const executor = callHttpInterceptors(interceptor, client); + const context = await executor(request, undefined); + expect(context.request.headers).toEqual(undefined); + expect(context.request.url).toEqual( + 'http://apimatic.hopto.org:3000/test/requestBuilder' + ); + }); + + it('should test OR scheme with disabled accessToken and disabled basicAuth', async () => { + try { + const request: HttpRequest = { + method: 'GET', + url: 'http://apimatic.hopto.org:3000/test/requestBuilder', + }; + const securityRequirements = [{ apiKey: false }, { apiHeader: false }]; + const provider = compositeAuthenticationProvider(authConfig); + const interceptor = [provider(securityRequirements)]; + const client = async (req) => { + return { request: req, response }; + }; + const executor = callHttpInterceptors(interceptor, client); + const context = await executor(request, undefined); + expect(context.request.headers).toEqual(undefined); + expect(context.request.auth).toEqual(undefined); + } catch (error) { + expect(error.message).toEqual( + 'Required authentication credentials for this API call are not provided or all provided auth combinations are disabled' + ); + } + }); +}); + +describe('test composite authentication adapter with security requirements combinations and provided authCredentials', () => { + const config: Configuration = { timeout: 60000, environment: 'Production', customUrl: 'https://connect.product.com', accessTokenCredentials: { - accessToken: '0b79bab50daca910bmmmd4f1a2b675d606555e222', + accessToken: '0b79bab50dacabmmmd4f1a2b675d606555e222', }, basicAuthCredentials: { username: 'Maryam', password: '123456', }, apiKeyCredentials: { - token: 'asdqwaxr2gSdhasWSDbdAgdA623ffghhhde7Adysi23', + token: 'asdqwaxr2gSdhasWSDbdA623ffghhhde7Adysi23', apiKey: 'api-key', }, apiHeaderCredentials: { - token: 'Qaws2W233tuyess4T56G6Vref2', + token: 'Qaws2W233tuyess6G6Vref2', apiKey: 'api-key', }, oAuthBearerTokenCredentials: { - accessToken: '0b79bab50daca54cchg12k000d4f1a2b675d604257e42', + accessToken: '0b79bab50daca54cchk000d4f1a2b675d604257e42', + }, + oAuthCCGCredentials: { + oAuthClientId: '23', + oAuthClientSecret: 'tQNSqQlXBIwZcY9auoujQ57ckDcoh3t8UPbBRkSF', + }, + oAuthACGCredentials: { + oAuthClientId: '24', + oAuthClientSecret: 'Y9auoujQ57ckDtQNSqQlXBIwZccoh3t8UPbBRkSF', + oAuthRedirectUri: '', }, }; @@ -50,6 +228,12 @@ describe('test composite Authentication Adapter', () => { oAuthBearerToken: config.oAuthBearerTokenCredentials && accessTokenAuthenticationProvider(config.oAuthBearerTokenCredentials), + oAuthCCG: + config.oAuthCCGCredentials && + requestAuthenticationProvider(config.oAuthCCGCredentials.oAuthToken), + oAuthACG: + config.oAuthACGCredentials && + requestAuthenticationProvider(config.oAuthACGCredentials.oAuthToken), }; const response: HttpResponse = { @@ -72,7 +256,7 @@ describe('test composite Authentication Adapter', () => { const executor = callHttpInterceptors(interceptor, client); const context = await executor(request, undefined); expect(context.request.headers).toEqual({ - authorization: 'Bearer 0b79bab50daca910bmmmd4f1a2b675d606555e222', + authorization: 'Bearer 0b79bab50dacabmmmd4f1a2b675d606555e222', }); expect(context.request.auth).toEqual(undefined); }); @@ -91,7 +275,7 @@ describe('test composite Authentication Adapter', () => { const executor = callHttpInterceptors(interceptor, client); const context = await executor(request, undefined); expect(context.request.headers).toEqual({ - authorization: 'Bearer 0b79bab50daca910bmmmd4f1a2b675d606555e222', + authorization: 'Bearer 0b79bab50dacabmmmd4f1a2b675d606555e222', }); expect(context.request.auth).toEqual(undefined); }); @@ -181,7 +365,7 @@ describe('test composite Authentication Adapter', () => { const executor = callHttpInterceptors(interceptor, client); const context = await executor(request, undefined); expect(context.request.headers).toEqual({ - authorization: 'Bearer 0b79bab50daca910bmmmd4f1a2b675d606555e222', + authorization: 'Bearer 0b79bab50dacabmmmd4f1a2b675d606555e222', }); expect(context.request.auth).toEqual({ username: 'Maryam', @@ -204,7 +388,7 @@ describe('test composite Authentication Adapter', () => { const executor = callHttpInterceptors(interceptor, client); const context = await executor(request, undefined); expect(context.request.headers).toEqual({ - authorization: 'Bearer 0b79bab50daca910bmmmd4f1a2b675d606555e222', + authorization: 'Bearer 0b79bab50dacabmmmd4f1a2b675d606555e222', }); } catch (error) { expect(error.message).toEqual( @@ -231,14 +415,14 @@ describe('test composite Authentication Adapter', () => { const context = await executor(request, undefined); expect(context.request.headers).toEqual({ apiKey: 'api-key', - token: 'Qaws2W233tuyess4T56G6Vref2', + token: 'Qaws2W233tuyess6G6Vref2', }); expect(context.request.auth).toEqual({ username: 'Maryam', password: '123456', }); expect(context.request.url).toEqual( - 'http://apimatic.hopto.org:3000/test/requestBuilder?token=asdqwaxr2gSdhasWSDbdAgdA623ffghhhde7Adysi23&apiKey=api-key' + 'http://apimatic.hopto.org:3000/test/requestBuilder?token=asdqwaxr2gSdhasWSDbdA623ffghhhde7Adysi23&apiKey=api-key' ); }); @@ -259,7 +443,68 @@ describe('test composite Authentication Adapter', () => { const executor = callHttpInterceptors(interceptor, client); const context = await executor(request, undefined); expect(context.request.headers).toEqual({ - authorization: 'Bearer 0b79bab50daca54cchg12k000d4f1a2b675d604257e42', + authorization: 'Bearer 0b79bab50daca54cchk000d4f1a2b675d604257e42', + }); + }); + + it('should test scheme combination with enabled oAuthACG oAuthCCG with unset access token', async () => { + try { + const request: HttpRequest = { + method: 'GET', + url: 'http://apimatic.hopto.org:3000/test/requestBuilder', + }; + const securityRequirements = [{ oAuthACG: true, oAuthCCG: true }]; + const provider = compositeAuthenticationProvider(authConfig); + const interceptor = [provider(securityRequirements)]; + const client = async (req) => { + return { request: req, response }; + }; + const executor = callHttpInterceptors(interceptor, client); + const context = await executor(request, undefined); + expect(context.request.headers).toEqual({ + authorization: 'Bearer 0b79bab50daca54cchk000d4f1a2b675d604257e42', + }); + } catch (error) { + expect(error.message).toEqual( + 'Client is not authorized. An OAuth token is needed to make API calls.' + ); + } + }); + + it('should test scheme combination with enabled oAuthACG oAuthCCG with set access token', async () => { + if (config.oAuthACGCredentials) { + config.oAuthACGCredentials.oAuthToken = { + accessToken: '1f12495f1a1ad9066b51fb3b4e456aee', + tokenType: 'Bearer', + expiresIn: BigInt(100000), + scope: '[products, orders]', + expiry: BigInt(Date.now()), + }; + } + + const auth1Config = { + oAuthCCG: + config.oAuthCCGCredentials && + requestAuthenticationProvider(config.oAuthCCGCredentials.oAuthToken), + oAuthACG: + config.oAuthACGCredentials && + requestAuthenticationProvider(config.oAuthACGCredentials.oAuthToken), + }; + + const request: HttpRequest = { + method: 'GET', + url: 'http://apimatic.hopto.org:3000/test/requestBuilder', + }; + const securityRequirements = [{ oAuthACG: true }, { oAuthCCG: true }]; + const provider = compositeAuthenticationProvider(auth1Config); + const interceptor = [provider(securityRequirements)]; + const client = async (req) => { + return { request: req, response }; + }; + const executor = callHttpInterceptors(interceptor, client); + const context = await executor(request, undefined); + expect(context.request.headers).toEqual({ + authorization: 'Bearer 1f12495f1a1ad9066b51fb3b4e456aee', }); }); });