diff --git a/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/GrpcAuthMode/index.js b/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/GrpcAuthMode/index.js index 3b95b708f..9819068b3 100644 --- a/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/GrpcAuthMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/GrpcAuthMode/index.js @@ -30,6 +30,10 @@ const GrpcAuthMode = ({ item, collection }) => { name: 'OAuth2', mode: 'oauth2' }, + { + name: 'WSSE Auth', + mode: 'wsse' + }, { name: 'Inherit', mode: 'inherit' diff --git a/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/index.js b/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/index.js index b30be89e5..9f9ea9070 100644 --- a/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/index.js +++ b/packages/bruno-app/src/components/RequestPane/GrpcRequestPane/GrpcAuth/index.js @@ -6,6 +6,7 @@ import BearerAuth from '../../Auth/BearerAuth'; import BasicAuth from '../../Auth/BasicAuth'; import ApiKeyAuth from '../../Auth/ApiKeyAuth'; import OAuth2 from '../../Auth/OAuth2/index'; +import WsseAuth from '../../Auth/WsseAuth'; import StyledWrapper from './StyledWrapper'; import { humanizeRequestAuthMode } from 'utils/collections'; import { getTreePathFromCollectionToItem } from 'utils/collections/index'; @@ -13,7 +14,10 @@ import { updateRequestAuthMode, updateAuth } from 'providers/ReduxStore/slices/c import { saveRequest } from 'providers/ReduxStore/slices/collections/actions'; // List of auth modes supported by gRPC -const supportedGrpcAuthModes = ['basic', 'bearer', 'apikey', 'oauth2', 'none', 'inherit']; +// Note: Only header-based auth modes work with gRPC +// Complex auth modes like AWS Sig v4, Digest, and NTLM require axios interceptors +// and cannot be supported in gRPC requests as of now +const supportedGrpcAuthModes = ['basic', 'bearer', 'apikey', 'oauth2', 'wsse', 'none', 'inherit']; const GrpcAuth = ({ item, collection }) => { const dispatch = useDispatch(); @@ -83,6 +87,9 @@ const GrpcAuth = ({ item, collection }) => { case 'oauth2': { return ; } + case 'wsse': { + return ; + } case 'inherit': { const source = getEffectiveAuthSource(); diff --git a/packages/bruno-electron/src/ipc/network/grpc-event-handlers.js b/packages/bruno-electron/src/ipc/network/grpc-event-handlers.js index cc3ad9add..c6e95e452 100644 --- a/packages/bruno-electron/src/ipc/network/grpc-event-handlers.js +++ b/packages/bruno-electron/src/ipc/network/grpc-event-handlers.js @@ -11,118 +11,7 @@ const { getProcessEnvVars } = require('../../store/process-env'); const { getOAuth2TokenUsingPasswordCredentials, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingAuthorizationCode } = require('../../utils/oauth2'); const { interpolateString } = require('./interpolate-string'); const path = require('node:path'); - -const setGrpcAuthHeaders = (grpcRequest, request, collectionRoot) => { - const collectionAuth = get(collectionRoot, 'request.auth'); - if (collectionAuth && request.auth?.mode === 'inherit') { - if (collectionAuth.mode === 'basic') { - grpcRequest.basicAuth = { - username: get(collectionAuth, 'basic.username'), - password: get(collectionAuth, 'basic.password') - }; - } - - if (collectionAuth.mode === 'bearer') { - grpcRequest.headers['Authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; - } - - if (collectionAuth.mode === 'apikey') { - grpcRequest.headers[collectionAuth.apikey?.key] = collectionAuth.apikey?.value; - - } - - if (collectionAuth.mode === 'oauth2') { - const grantType = get(collectionAuth, 'oauth2.grantType'); - - if (grantType === 'client_credentials') { - grpcRequest.oauth2 = { - grantType, - accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'), - clientId: get(collectionAuth, 'oauth2.clientId'), - clientSecret: get(collectionAuth, 'oauth2.clientSecret'), - scope: get(collectionAuth, 'oauth2.scope'), - credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'), - tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'), - tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'), - tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey') - }; - } else if (grantType === 'password') { - grpcRequest.oauth2 = { - grantType, - accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'), - username: get(collectionAuth, 'oauth2.username'), - password: get(collectionAuth, 'oauth2.password'), - clientId: get(collectionAuth, 'oauth2.clientId'), - clientSecret: get(collectionAuth, 'oauth2.clientSecret'), - scope: get(collectionAuth, 'oauth2.scope'), - credentialsPlacement: get(collectionAuth, 'oauth2.credentialsPlacement'), - tokenPlacement: get(collectionAuth, 'oauth2.tokenPlacement'), - tokenHeaderPrefix: get(collectionAuth, 'oauth2.tokenHeaderPrefix'), - tokenQueryKey: get(collectionAuth, 'oauth2.tokenQueryKey') - }; - } - } - - } - - if (request.auth && request.auth.mode !== 'inherit') { - if (request.auth.mode === 'basic') { - grpcRequest.basicAuth = { - username: get(request, 'auth.basic.username'), - password: get(request, 'auth.basic.password') - }; - } - - if (request.auth.mode === 'bearer') { - grpcRequest.headers['Authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; - } - - if (request.auth.mode === 'oauth2') { - const grantType = get(request, 'auth.oauth2.grantType'); - - - if (grantType === 'client_credentials') { - grpcRequest.oauth2 = { - grantType, - clientId: get(request, 'auth.oauth2.clientId'), - clientSecret: get(request, 'auth.oauth2.clientSecret'), - scope: get(request, 'auth.oauth2.scope'), - accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), - tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'), - credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'), - tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'), - tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey') - }; - } else if (grantType === 'password') { - grpcRequest.oauth2 = { - grantType, - username: get(request, 'auth.oauth2.username'), - password: get(request, 'auth.oauth2.password'), - clientId: get(request, 'auth.oauth2.clientId'), - clientSecret: get(request, 'auth.oauth2.clientSecret'), - scope: get(request, 'auth.oauth2.scope'), - accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), - tokenPlacement: get(request, 'auth.oauth2.tokenPlacement'), - credentialsPlacement: get(request, 'auth.oauth2.credentialsPlacement'), - tokenHeaderPrefix: get(request, 'auth.oauth2.tokenHeaderPrefix'), - tokenQueryKey: get(request, 'auth.oauth2.tokenQueryKey') - }; - } else if (grantType === 'authorization_code') { - grpcRequest.oauth2 = { - grantType, - ...get(request, 'auth.oauth2') - }; - } - } - - if (request.auth.mode === 'apikey') { - grpcRequest.headers[request.auth.apikey?.key] = request.auth.apikey?.value; - } - } - - - return grpcRequest; -} +const { setAuthHeaders } = require('./prepare-request'); const prepareRequest = async (item, collection, environment, runtimeVariables, certsAndProxyConfig = {}) => { const request = item.draft ? item.draft.request : item.request; @@ -182,7 +71,7 @@ const prepareRequest = async (item, collection, environment, runtimeVariables, c oauth2CredentialVariables: request.oauth2CredentialVariables, } - grpcRequest = setGrpcAuthHeaders(grpcRequest, request, collectionRoot); + grpcRequest = setAuthHeaders(grpcRequest, request, collectionRoot); if (grpcRequest.oauth2) { let requestCopy = cloneDeep(grpcRequest); diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 375422164..09d03c85f 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -43,8 +43,8 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { }; break; case 'wsse': - const username = get(request, 'auth.wsse.username', ''); - const password = get(request, 'auth.wsse.password', ''); + const username = get(collectionAuth, 'wsse.username', ''); + const password = get(collectionAuth, 'wsse.password', ''); const ts = new Date().toISOString(); const nonce = crypto.randomBytes(16).toString('hex'); @@ -193,7 +193,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { axiosRequest.oauth2 = { grantType: grantType, accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), - refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'), + refreshTokenUrl: get(request, 'auth.oauth2.refreshTokenUrl'), username: get(request, 'auth.oauth2.username'), password: get(request, 'auth.oauth2.password'), clientId: get(request, 'auth.oauth2.clientId'), @@ -215,7 +215,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { callbackUrl: get(request, 'auth.oauth2.callbackUrl'), authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'), accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), - refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'), + refreshTokenUrl: get(request, 'auth.oauth2.refreshTokenUrl'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), scope: get(request, 'auth.oauth2.scope'), @@ -251,7 +251,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { axiosRequest.oauth2 = { grantType: grantType, accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'), - refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'), + refreshTokenUrl: get(request, 'auth.oauth2.refreshTokenUrl'), clientId: get(request, 'auth.oauth2.clientId'), clientSecret: get(request, 'auth.oauth2.clientSecret'), scope: get(request, 'auth.oauth2.scope'), diff --git a/packages/bruno-electron/tests/prepare-request.test.js b/packages/bruno-electron/tests/prepare-request.test.js new file mode 100644 index 000000000..9830b45c3 --- /dev/null +++ b/packages/bruno-electron/tests/prepare-request.test.js @@ -0,0 +1,959 @@ +const crypto = require('node:crypto'); + +// Mock crypto.randomBytes to return predictable values for testing +jest.mock('node:crypto', () => ({ + ...jest.requireActual('node:crypto'), + randomBytes: jest.fn(() => Buffer.from('1234567890abcdef', 'hex')) +})); + +// Mock the lodash get function with a more sophisticated mock +const mockGet = jest.fn(); +jest.mock('lodash', () => ({ + get: mockGet, + each: jest.fn(), + filter: jest.fn(), + find: jest.fn() +})); + +// Import the function to test +const { setAuthHeaders } = require('../src/ipc/network/prepare-request'); + +describe('setAuthHeaders', () => { + let mockAxiosRequest; + let mockRequest; + let mockCollectionRoot; + + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks(); + + // Reset crypto mock to return predictable values + crypto.randomBytes.mockReturnValue(Buffer.from('1234567890abcdef', 'hex')); + + // Setup default mock objects + mockAxiosRequest = { + headers: {} + }; + + mockRequest = { + auth: { + mode: 'none' + } + }; + + mockCollectionRoot = { + request: { + auth: null + } + }; + + // Setup a more sophisticated mock for lodash get function + mockGet.mockImplementation((obj, path, defaultValue) => { + if (!obj) return defaultValue; + + const keys = path.split('.'); + let current = obj; + + for (const key of keys) { + if (current && typeof current === 'object' && key in current) { + current = current[key]; + } else { + return defaultValue; + } + } + + return current; + }); + }); + + describe('Collection-level authentication inheritance', () => { + test('should inherit AWS v4 authentication from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'awsv4', + awsv4: { + accessKeyId: 'test-access-key', + secretAccessKey: 'test-secret-key', + sessionToken: 'test-session-token', + service: 's3', + region: 'us-east-1', + profileName: 'default' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.awsv4config).toEqual({ + accessKeyId: 'test-access-key', + secretAccessKey: 'test-secret-key', + sessionToken: 'test-session-token', + service: 's3', + region: 'us-east-1', + profileName: 'default' + }); + }); + + test('should inherit basic authentication from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'basic', + basic: { + username: 'testuser', + password: 'testpass' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.basicAuth).toEqual({ + username: 'testuser', + password: 'testpass' + }); + }); + + test('should inherit bearer authentication from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'bearer', + bearer: { + token: 'test-token' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.headers['Authorization']).toBe('Bearer test-token'); + }); + + test('should inherit digest authentication from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'digest', + digest: { + username: 'testuser', + password: 'testpass' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.digestConfig).toEqual({ + username: 'testuser', + password: 'testpass' + }); + }); + + test('should inherit NTLM authentication from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'ntlm', + ntlm: { + username: 'testuser', + password: 'testpass', + domain: 'testdomain' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.ntlmConfig).toEqual({ + username: 'testuser', + password: 'testpass', + domain: 'testdomain' + }); + }); + + test('should inherit WSSE authentication from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'wsse', + wsse: { + username: 'testuser', + password: 'testpass' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.headers['X-WSSE']).toMatch(/UsernameToken Username="testuser", PasswordDigest="[^"]+", Nonce="1234567890abcdef", Created="[^"]+"/); + }); + + test('should inherit API key authentication from collection (header placement)', () => { + mockCollectionRoot.request.auth = { + mode: 'apikey', + apikey: { + key: 'X-API-Key', + value: 'test-api-key', + placement: 'header' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.headers['X-API-Key']).toBe('test-api-key'); + }); + + test('should inherit API key authentication from collection (query params placement)', () => { + mockCollectionRoot.request.auth = { + mode: 'apikey', + apikey: { + key: 'api_key', + value: 'test-api-key', + placement: 'queryparams' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.apiKeyAuthValueForQueryParams).toEqual({ + key: 'api_key', + value: 'test-api-key', + placement: 'queryparams' + }); + }); + + test('should skip API key authentication when key is empty', () => { + mockCollectionRoot.request.auth = { + mode: 'apikey', + apikey: { + key: '', + value: 'test-api-key', + placement: 'header' + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.headers['']).toBeUndefined(); + }); + }); + + describe('OAuth2 authentication inheritance', () => { + test('should inherit OAuth2 password grant from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'password', + accessTokenUrl: 'https://example.com/token', + refreshTokenUrl: 'https://example.com/refresh', + username: 'testuser', + password: 'testpass', + clientId: 'test-client', + clientSecret: 'test-secret', + scope: 'read write', + credentialsPlacement: 'body', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'password', + accessTokenUrl: 'https://example.com/token', + refreshTokenUrl: 'https://example.com/refresh', + username: 'testuser', + password: 'testpass', + clientId: 'test-client', + clientSecret: 'test-secret', + scope: 'read write', + credentialsPlacement: 'body', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + + test('should inherit OAuth2 authorization_code grant from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'authorization_code', + callbackUrl: 'https://example.com/callback', + authorizationUrl: 'https://example.com/auth', + accessTokenUrl: 'https://example.com/token', + refreshTokenUrl: 'https://example.com/refresh', + clientId: 'test-client', + clientSecret: 'test-secret', + scope: 'read write', + state: 'random-state', + pkce: true, + credentialsPlacement: 'body', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'authorization_code', + callbackUrl: 'https://example.com/callback', + authorizationUrl: 'https://example.com/auth', + accessTokenUrl: 'https://example.com/token', + refreshTokenUrl: 'https://example.com/refresh', + clientId: 'test-client', + scope: 'read write', + state: 'random-state', + pkce: true, + credentialsPlacement: 'body', + clientSecret: 'test-secret', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + + test('should inherit OAuth2 implicit grant from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'implicit', + callbackUrl: 'https://example.com/callback', + authorizationUrl: 'https://example.com/auth', + clientId: 'test-client', + scope: 'read write', + state: 'random-state', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'implicit', + callbackUrl: 'https://example.com/callback', + authorizationUrl: 'https://example.com/auth', + clientId: 'test-client', + scope: 'read write', + state: 'random-state', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + + test('should inherit OAuth2 client_credentials grant from collection', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'client_credentials', + accessTokenUrl: 'https://example.com/token', + refreshTokenUrl: 'https://example.com/refresh', + clientId: 'test-client', + clientSecret: 'test-secret', + scope: 'read write', + credentialsPlacement: 'body', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + mockRequest.auth.mode = 'inherit'; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'client_credentials', + accessTokenUrl: 'https://example.com/token', + refreshTokenUrl: 'https://example.com/refresh', + clientId: 'test-client', + clientSecret: 'test-secret', + scope: 'read write', + credentialsPlacement: 'body', + credentialsId: 'test-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + }); + + describe('Request-level authentication (overrides collection)', () => { + test('should set AWS v4 authentication at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'awsv4', + awsv4: { + accessKeyId: 'test-access-key', + secretAccessKey: 'test-secret-key', + sessionToken: 'test-session-token', + service: 's3', + region: 'us-east-1', + profileName: 'default' + } + } + mockRequest.auth = { + mode: 'awsv4', + awsv4: { + accessKeyId: 'request-access-key', + secretAccessKey: 'request-secret-key', + sessionToken: 'request-session-token', + service: 's3', + region: 'us-west-2', + profileName: 'production' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.awsv4config).toEqual({ + accessKeyId: 'request-access-key', + secretAccessKey: 'request-secret-key', + sessionToken: 'request-session-token', + service: 's3', + region: 'us-west-2', + profileName: 'production' + }); + }); + + test('should set basic authentication at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'basic', + basic: { + username: 'testuser', + password: 'testpass' + } + }; + mockRequest.auth = { + mode: 'basic', + basic: { + username: 'requestuser', + password: 'requestpass' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.basicAuth).toEqual({ + username: 'requestuser', + password: 'requestpass' + }); + }); + + test('should set bearer authentication at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'bearer', + bearer: { + token: 'test-token' + } + }; + mockRequest.auth = { + mode: 'bearer', + bearer: { + token: 'request-token' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.headers['Authorization']).toBe('Bearer request-token'); + }); + + test('should set digest authentication at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'digest', + digest: { + username: 'testuser', + password: 'testpass' + } + }; + mockRequest.auth = { + mode: 'digest', + digest: { + username: 'requestuser', + password: 'requestpass' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.digestConfig).toEqual({ + username: 'requestuser', + password: 'requestpass' + }); + }); + + test('should set NTLM authentication at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'ntlm', + ntlm: { + username: 'testuser', + password: 'testpass', + domain: 'testdomain' + } + }; + mockRequest.auth = { + mode: 'ntlm', + ntlm: { + username: 'requestuser', + password: 'requestpass', + domain: 'requestdomain' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.ntlmConfig).toEqual({ + username: 'requestuser', + password: 'requestpass', + domain: 'requestdomain' + }); + }); + + test('should set WSSE authentication at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'wsse', + wsse: { + username: 'testuser', + password: 'testpass' + } + }; + mockRequest.auth = { + mode: 'wsse', + wsse: { + username: 'requestuser', + password: 'requestpass' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.headers['X-WSSE']).toMatch(/UsernameToken Username="requestuser", PasswordDigest="[^"]+", Nonce="1234567890abcdef", Created="[^"]+"/); + }); + + test('should set API key authentication at request level (header placement)', () => { + mockCollectionRoot.request.auth = { + mode: 'apikey', + apikey: { + key: 'X-Request-API-Key', + value: 'test-api-key', + placement: 'header' + } + }; + mockRequest.auth = { + mode: 'apikey', + apikey: { + key: 'X-Request-API-Key', + value: 'request-api-key', + placement: 'header' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.headers['X-Request-API-Key']).toBe('request-api-key'); + }); + + test('should set API key authentication at request level (query params placement)', () => { + mockCollectionRoot.request.auth = { + mode: 'apikey', + apikey: { + key: 'X-Request-API-Key', + value: 'test-api-key', + placement: 'header' + } + }; + mockRequest.auth = { + mode: 'apikey', + apikey: { + key: 'request_api_key', + value: 'request-api-key', + placement: 'queryparams' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.apiKeyAuthValueForQueryParams).toEqual({ + key: 'request_api_key', + value: 'request-api-key', + placement: 'queryparams' + }); + }); + + test('should set OAuth2 password grant at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'password', + accessTokenUrl: 'https://collection.com/token', + refreshTokenUrl: 'https://collection.com/refresh', + username: 'collectionuser', + password: 'collectionpass', + clientId: 'collection-client', + clientSecret: 'collection-secret', + scope: 'read', + credentialsPlacement: 'header', + credentialsId: 'collection-credentials', + tokenPlacement: 'query', + tokenHeaderPrefix: 'Token', + tokenQueryKey: 'token', + autoFetchToken: false, + autoRefreshToken: false, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + mockRequest.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'password', + accessTokenUrl: 'https://request.com/token', + refreshTokenUrl: 'https://request.com/refresh', + username: 'requestuser', + password: 'requestpass', + clientId: 'request-client', + clientSecret: 'request-secret', + scope: 'read', + credentialsPlacement: 'header', + credentialsId: 'request-credentials', + tokenPlacement: 'query', + tokenHeaderPrefix: 'Token', + tokenQueryKey: 'token', + autoFetchToken: false, + autoRefreshToken: false, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'password', + accessTokenUrl: 'https://request.com/token', + refreshTokenUrl: 'https://request.com/refresh', + username: 'requestuser', + password: 'requestpass', + clientId: 'request-client', + clientSecret: 'request-secret', + scope: 'read', + credentialsPlacement: 'header', + credentialsId: 'request-credentials', + tokenPlacement: 'query', + tokenHeaderPrefix: 'Token', + tokenQueryKey: 'token', + autoFetchToken: false, + autoRefreshToken: false, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + + test('should set OAuth2 authorization_code grant at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'password', + callbackUrl: 'https://collection.com/callback', + authorizationUrl: 'https://collection.com/auth', + accessTokenUrl: 'https://collection.com/token', + refreshTokenUrl: 'https://collection.com/refresh', + username: 'collectionuser', + password: 'collectionpass', + clientId: 'collection-client', + clientSecret: 'collection-secret', + } + }; + mockRequest.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'authorization_code', + callbackUrl: 'https://request.com/callback', + authorizationUrl: 'https://request.com/auth', + accessTokenUrl: 'https://request.com/token', + refreshTokenUrl: 'https://request.com/refresh', + clientId: 'request-client', + clientSecret: 'request-secret', + scope: 'read', + state: 'request-state', + pkce: false, + credentialsPlacement: 'body', + credentialsId: 'request-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'authorization_code', + callbackUrl: 'https://request.com/callback', + authorizationUrl: 'https://request.com/auth', + accessTokenUrl: 'https://request.com/token', + refreshTokenUrl: 'https://request.com/refresh', + clientId: 'request-client', + clientSecret: 'request-secret', + scope: 'read', + state: 'request-state', + pkce: false, + credentialsPlacement: 'body', + credentialsId: 'request-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + + test('should set OAuth2 implicit grant at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'implicit', + callbackUrl: 'https://collection.com/callback', + authorizationUrl: 'https://collection.com/auth', + clientId: 'collection-client', + scope: 'read', + state: 'collection-state', + credentialsId: 'collection-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + mockRequest.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'implicit', + callbackUrl: 'https://request.com/callback', + authorizationUrl: 'https://request.com/auth', + clientId: 'request-client', + scope: 'read', + state: 'request-state', + credentialsId: 'request-credentials', + tokenPlacement: 'query', + tokenHeaderPrefix: 'Token', + tokenQueryKey: 'token', + autoFetchToken: false, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'implicit', + callbackUrl: 'https://request.com/callback', + authorizationUrl: 'https://request.com/auth', + clientId: 'request-client', + credentialsId: 'request-credentials', + scope: 'read', + state: 'request-state', + tokenPlacement: 'query', + tokenHeaderPrefix: 'Token', + tokenQueryKey: 'token', + autoFetchToken: false, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + + test('should set OAuth2 client_credentials grant at request level', () => { + mockCollectionRoot.request.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'client_credentials', + accessTokenUrl: 'https://collection.com/token', + refreshTokenUrl: 'https://collection.com/refresh', + clientId: 'collection-client', + clientSecret: 'collection-secret', + scope: 'read', + credentialsPlacement: 'body', + credentialsId: 'collection-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + mockRequest.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'client_credentials', + accessTokenUrl: 'https://request.com/token', + refreshTokenUrl: 'https://request.com/refresh', + clientId: 'request-client', + clientSecret: 'request-secret', + scope: 'read', + credentialsPlacement: 'body', + credentialsId: 'request-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.oauth2).toEqual({ + grantType: 'client_credentials', + accessTokenUrl: 'https://request.com/token', + refreshTokenUrl: 'https://request.com/refresh', + clientId: 'request-client', + clientSecret: 'request-secret', + scope: 'read', + credentialsPlacement: 'body', + credentialsId: 'request-credentials', + tokenPlacement: 'header', + tokenHeaderPrefix: 'Bearer', + tokenQueryKey: 'access_token', + autoFetchToken: true, + autoRefreshToken: true, + additionalParameters: { authorization: [], token: [], refresh: [] } + }); + }); + }); + + describe('Edge cases and error handling', () => { + test('should handle missing collection auth gracefully', () => { + mockCollectionRoot.request.auth = null; + mockRequest.auth = { + mode: 'basic', + basic: { + username: 'testuser', + password: 'testpass' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result.basicAuth).toEqual({ + username: 'testuser', + password: 'testpass' + }); + }); + + test('should handle missing request auth gracefully', () => { + mockRequest.auth = null; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result).toBe(mockAxiosRequest); + expect(result.headers).toEqual({}); + }); + + test('should handle missing auth mode gracefully', () => { + mockRequest.auth = {}; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result).toBe(mockAxiosRequest); + expect(result.headers).toEqual({}); + }); + + test('should handle unknown auth mode gracefully', () => { + mockRequest.auth = { + mode: 'unknown' + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result).toBe(mockAxiosRequest); + expect(result.headers).toEqual({}); + }); + + test('should handle missing OAuth2 grant type gracefully', () => { + mockRequest.auth = { + mode: 'oauth2', + oauth2: {} + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result).toBe(mockAxiosRequest); + expect(result.oauth2).toBeUndefined(); + }); + + test('should handle unknown OAuth2 grant type gracefully', () => { + mockRequest.auth = { + mode: 'oauth2', + oauth2: { + grantType: 'unknown_grant' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result).toBe(mockAxiosRequest); + expect(result.oauth2).toBeUndefined(); + }); + + test('should return the modified axiosRequest object', () => { + mockRequest.auth = { + mode: 'bearer', + bearer: { + token: 'test-token' + } + }; + + const result = setAuthHeaders(mockAxiosRequest, mockRequest, mockCollectionRoot); + + expect(result).toBe(mockAxiosRequest); + expect(result.headers['Authorization']).toBe('Bearer test-token'); + }); + }); +});