mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: add new parameter "apiKeyHeaderName" for "apikey" mode (#7762)
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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') {
|
||||
|
||||
31
packages/bruno-cli/tests/runner/interpolate-vars.spec.js
Normal file
31
packages/bruno-cli/tests/runner/interpolate-vars.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
53
packages/bruno-js/tests/bruno-request-auth-mode.spec.js
Normal file
53
packages/bruno-js/tests/bruno-request-auth-mode.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
20
tests/auth/apikey/apikey-runner.spec.ts
Normal file
20
tests/auth/apikey/apikey-runner.spec.ts
Normal 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
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "apikey-auth-mode-test",
|
||||
"type": "collection"
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
11
tests/auth/apikey/init-user-data/preferences.json
Normal file
11
tests/auth/apikey/init-user-data/preferences.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"lastOpenedCollections": [
|
||||
"{{collectionPath}}/apikey-auth-mode-test"
|
||||
],
|
||||
"preferences": {
|
||||
"onboarding": {
|
||||
"hasLaunchedBefore": true,
|
||||
"hasSeenWelcomeModal": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user