feat: add new parameter "apiKeyHeaderName" for "apikey" mode (#7762)

This commit is contained in:
prateek-bruno
2026-04-28 11:23:16 +05:30
committed by GitHub
parent ac2cff90f0
commit a04d434f76
14 changed files with 202 additions and 7 deletions

View File

@@ -65,6 +65,9 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
delete request.headers[key];
request.headers[_interpolate(key)] = _interpolate(value);
});
if (request.apiKeyHeaderName) {
request.apiKeyHeaderName = _interpolate(request.apiKeyHeaderName);
}
const contentType = getContentType(request.headers);
const isGraphqlRequest = request.mode === 'graphql';

View File

@@ -67,6 +67,7 @@ const prepareRequest = async (item = {}, collection = {}) => {
if (collectionAuth.mode === 'apikey') {
if (collectionAuth.apikey?.placement === 'header') {
axiosRequest.headers[collectionAuth.apikey?.key] = collectionAuth.apikey?.value;
axiosRequest.apiKeyHeaderName = collectionAuth.apikey?.key;
}
if (collectionAuth.apikey?.placement === 'queryparams') {
@@ -309,6 +310,7 @@ const prepareRequest = async (item = {}, collection = {}) => {
if (request.auth.mode === 'apikey') {
if (request.auth.apikey?.placement === 'header') {
axiosRequest.headers[request.auth.apikey?.key] = request.auth.apikey?.value;
axiosRequest.apiKeyHeaderName = request.auth.apikey?.key;
}
if (request.auth.apikey?.placement === 'queryparams') {

View File

@@ -0,0 +1,31 @@
const { describe, it, expect } = require('@jest/globals');
const interpolateVars = require('../../src/runner/interpolate-vars');
describe('interpolate-vars: api key header name sidecar', () => {
it('interpolates apiKeyHeaderName in lockstep with interpolated header keys', () => {
const request = {
url: 'https://example.com',
mode: 'none',
headers: {
'{{api_header_name}}': '{{api_key_value}}'
},
apiKeyHeaderName: '{{api_header_name}}',
pathParams: []
};
interpolateVars(
request,
{
api_header_name: 'X-API-Key',
api_key_value: 'secret-key-value'
},
{},
{}
);
expect(request.headers).toEqual({
'X-API-Key': 'secret-key-value'
});
expect(request.apiKeyHeaderName).toEqual('X-API-Key');
});
});

View File

@@ -72,6 +72,7 @@ describe('prepare-request: prepareRequest', () => {
const result = await prepareRequest(item, collection);
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
expect(result.apiKeyHeaderName).toEqual('x-api-key');
});
it('If collection auth is apikey in header and request has existing headers', async () => {
@@ -88,6 +89,7 @@ describe('prepare-request: prepareRequest', () => {
const result = await prepareRequest(item, collection);
expect(result.headers).toHaveProperty('Content-Type', 'application/json');
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
expect(result.apiKeyHeaderName).toEqual('x-api-key');
});
it('If collection auth is apikey in query parameters', async () => {
@@ -106,6 +108,7 @@ describe('prepare-request: prepareRequest', () => {
const expected = urlObj.toString();
const result = await prepareRequest(item, collection);
expect(result.url).toEqual(expected);
expect(result.apiKeyHeaderName).toBeUndefined();
});
});
@@ -355,6 +358,7 @@ describe('prepare-request: prepareRequest', () => {
const result = await prepareRequest(item);
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
expect(result.apiKeyHeaderName).toEqual('x-api-key');
});
it('If request auth is apikey in header and request has existing headers', async () => {
@@ -371,6 +375,7 @@ describe('prepare-request: prepareRequest', () => {
const result = await prepareRequest(item);
expect(result.headers).toHaveProperty('Content-Type', 'application/json');
expect(result.headers).toHaveProperty('x-api-key', '{{apiKey}}');
expect(result.apiKeyHeaderName).toEqual('x-api-key');
});
it('If request auth is apikey in query parameters', async () => {
@@ -389,6 +394,7 @@ describe('prepare-request: prepareRequest', () => {
const expected = urlObj.toString();
const result = await prepareRequest(item);
expect(result.url).toEqual(expected);
expect(result.apiKeyHeaderName).toBeUndefined();
});
});

View File

@@ -240,7 +240,7 @@ const configureRequest = async (
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
} catch (error) { }
}
}
break;
@@ -257,7 +257,7 @@ const configureRequest = async (
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
} catch (error) { }
}
}
break;
@@ -274,7 +274,7 @@ const configureRequest = async (
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
} catch (error) { }
}
}
break;
@@ -291,7 +291,7 @@ const configureRequest = async (
const url = new URL(request.url);
url.searchParams.set(tokenQueryKey, tokenValue);
request.url = url.toString();
} catch (error) {}
} catch (error) { }
}
}
break;
@@ -355,9 +355,6 @@ const configureRequest = async (
request.url = urlObj.toString();
}
// Remove apiKeyAuthValueForQueryParams, already interpolated and added to URL
delete request.apiKeyAuthValueForQueryParams;
return axiosInstance;
};

View File

@@ -72,6 +72,9 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
delete request.headers[key];
request.headers[_interpolate(key)] = _interpolate(value);
});
if (request.apiKeyHeaderName) {
request.apiKeyHeaderName = _interpolate(request.apiKeyHeaderName);
}
const contentType = getContentType(request.headers);
const isGraphqlRequest = request.mode === 'graphql';

View File

@@ -85,6 +85,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
if (apiKeyAuth.key.length === 0) break;
if (apiKeyAuth.placement === 'header') {
axiosRequest.headers[apiKeyAuth.key] = apiKeyAuth.value;
axiosRequest.apiKeyHeaderName = apiKeyAuth.key;
} else if (apiKeyAuth.placement === 'queryparams') {
// If the API key authentication is set and its placement is 'queryparams', add it to the axios request object. This will be used in the configureRequest function to append the API key to the query parameters of the request URL.
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
@@ -338,6 +339,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
if (apiKeyAuth.key.length === 0) break;
if (apiKeyAuth.placement === 'header') {
axiosRequest.headers[apiKeyAuth.key] = apiKeyAuth.value;
axiosRequest.apiKeyHeaderName = apiKeyAuth.key;
} else if (apiKeyAuth.placement === 'queryparams') {
// If the API key authentication is set and its placement is 'queryparams', add it to the axios request object. This will be used in the configureRequest function to append the API key to the query parameters of the request URL.
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;

View File

@@ -102,6 +102,10 @@ class BrunoRequest {
return 'bearer';
} else if (this.headers?.['Authorization']?.startsWith('Basic') || this.req?.auth?.username) {
return 'basic';
} else if (this.req?.apiKeyAuthValueForQueryParams) {
return 'apikey';
} else if (this.req?.apiKeyHeaderName && this.headers?.[this.req.apiKeyHeaderName] !== undefined) {
return 'apikey';
} else if (this.req?.awsv4) {
return 'awsv4';
} else if (this.req?.digestConfig) {

View File

@@ -0,0 +1,53 @@
const { describe, it, expect } = require('@jest/globals');
const BrunoRequest = require('../src/bruno-request');
const makeReq = (overrides = {}) => ({
url: 'http://localhost:5000/api',
method: 'GET',
headers: {
'Content-Type': 'application/json',
...overrides.headers
},
data: undefined,
...overrides
});
describe('BrunoRequest - getAuthMode()', () => {
it('returns apikey for header placement when the api key header is present', () => {
const req = new BrunoRequest(
makeReq({
headers: { 'X-API-Key': 'secret' },
apiKeyHeaderName: 'X-API-Key'
})
);
expect(req.getAuthMode()).toBe('apikey');
});
it('returns none when pre-request script deletes the api key header', () => {
const req = new BrunoRequest(
makeReq({
headers: { 'X-API-Key': 'secret' },
apiKeyHeaderName: 'X-API-Key'
})
);
req.deleteHeader('X-API-Key');
expect(req.getAuthMode()).toBe('none');
});
it('returns apikey for queryparams placement marker', () => {
const req = new BrunoRequest(
makeReq({
apiKeyAuthValueForQueryParams: {
key: 'api_key',
value: 'secret',
placement: 'queryparams'
}
})
);
expect(req.getAuthMode()).toBe('apikey');
});
});

View File

@@ -0,0 +1,20 @@
import { test } from '../../../playwright';
import { setSandboxMode, runCollection, validateRunnerResults } from '../../utils/page';
const COLLECTION_NAME = 'apikey-auth-mode-test';
test.describe.serial('API Key Auth Mode Runner', () => {
for (const mode of ['safe', 'developer'] as const) {
test(`detects API key auth in ${mode} mode`, async ({ pageWithUserData: page }) => {
await setSandboxMode(page, COLLECTION_NAME, mode);
await runCollection(page, COLLECTION_NAME);
await validateRunnerResults(page, {
totalRequests: 2,
passed: 2,
failed: 0,
skipped: 0
});
});
}
});

View File

@@ -0,0 +1,5 @@
{
"version": "1",
"name": "apikey-auth-mode-test",
"type": "collection"
}

View File

@@ -0,0 +1,29 @@
meta {
name: dynamic-header-key
type: http
seq: 1
}
get {
url: http://localhost:8081/ping
body: none
auth: apikey
}
auth:apikey {
key: {{api_key_header_name}}
value: {{api_key_value}}
placement: header
}
vars:pre-request {
api_key_header_name: X-API-Key
api_key_value: secret-key-value
}
tests {
test("detects API key auth when the header key is interpolated", function() {
expect(req.getAuthMode()).to.equal("apikey");
expect(req.getHeader("X-API-Key")).to.equal("secret-key-value");
});
}

View File

@@ -0,0 +1,29 @@
meta {
name: dynamic-query-key
type: http
seq: 2
}
get {
url: http://localhost:8081/ping
body: none
auth: apikey
}
auth:apikey {
key: {{api_key_query_name}}
value: {{api_key_value}}
placement: queryparams
}
vars:pre-request {
api_key_query_name: api_key
api_key_value: secret-key-value
}
tests {
test("detects API key auth when the query key is interpolated", function() {
expect(req.getAuthMode()).to.equal("apikey");
expect(req.getUrl()).to.contain("api_key=secret-key-value");
});
}

View File

@@ -0,0 +1,11 @@
{
"lastOpenedCollections": [
"{{collectionPath}}/apikey-auth-mode-test"
],
"preferences": {
"onboarding": {
"hasLaunchedBefore": true,
"hasSeenWelcomeModal": true
}
}
}