diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js
index c7f157a43..878e40b00 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js
@@ -106,6 +106,15 @@ const AuthMode = ({ collection }) => {
>
API Key
+
{
+ dropdownTippyRef.current.hide();
+ onModeChange('edgegrid');
+ }}
+ >
+ Akamai EdgeGrid
+
{
diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/EdgeGridAuth/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/EdgeGridAuth/StyledWrapper.js
new file mode 100644
index 000000000..d66055ca9
--- /dev/null
+++ b/packages/bruno-app/src/components/CollectionSettings/Auth/EdgeGridAuth/StyledWrapper.js
@@ -0,0 +1,20 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ label {
+ font-size: 0.8125rem;
+ }
+
+ .single-line-editor-wrapper {
+ padding: 0.5rem 0;
+ border-radius: 3px;
+ border: solid 1px ${(props) => props.theme.input.border};
+ background-color: ${(props) => props.theme.input.bg};
+
+ &:focus-within {
+ border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
+ }
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/EdgeGridAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/EdgeGridAuth/index.js
new file mode 100644
index 000000000..56a43cd9d
--- /dev/null
+++ b/packages/bruno-app/src/components/CollectionSettings/Auth/EdgeGridAuth/index.js
@@ -0,0 +1,147 @@
+import React from 'react';
+import SensitiveFieldWarning from 'components/SensitiveFieldWarning';
+import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField';
+import get from 'lodash/get';
+import { useTheme } from 'providers/Theme';
+import { useDispatch } from 'react-redux';
+import SingleLineEditor from 'components/SingleLineEditor';
+import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
+import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
+import StyledWrapper from './StyledWrapper';
+
+const EdgeGridAuth = ({ collection }) => {
+ const dispatch = useDispatch();
+ const { storedTheme } = useTheme();
+
+ const edgeGridAuth = get(collection, 'root.request.auth.edgegrid', {});
+ const { isSensitive } = useDetectSensitiveField(collection);
+ const { showWarning: showClientSecretWarning, warningMessage: clientSecretWarningMessage } = isSensitive(edgeGridAuth?.client_secret);
+
+ const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
+
+ const handleFieldChange = (field, value) => {
+ dispatch(updateCollectionAuth({
+ mode: 'edgegrid',
+ collectionUid: collection.uid,
+ content: {
+ access_token: edgeGridAuth.access_token || '',
+ client_token: edgeGridAuth.client_token || '',
+ client_secret: edgeGridAuth.client_secret || '',
+ nonce: edgeGridAuth.nonce || '',
+ timestamp: edgeGridAuth.timestamp || '',
+ base_url: edgeGridAuth.base_url || '',
+ headers_to_sign: edgeGridAuth.headers_to_sign || '',
+ max_body_size: edgeGridAuth.max_body_size || '',
+ [field]: value || ''
+ }
+ }));
+ };
+
+ return (
+
+
+
+ handleFieldChange('access_token', val)}
+ collection={collection}
+ />
+
+
+
+
+ handleFieldChange('client_token', val)}
+ collection={collection}
+ />
+
+
+
+
+ handleFieldChange('client_secret', val)}
+ collection={collection}
+ isSecret={true}
+ />
+ {showClientSecretWarning && }
+
+
+
+
+ handleFieldChange('base_url', val)}
+ collection={collection}
+ />
+
+
+
+
+ handleFieldChange('nonce', val)}
+ collection={collection}
+ />
+
+
+
+
+ handleFieldChange('timestamp', val)}
+ collection={collection}
+ />
+
+
+
+
+ handleFieldChange('headers_to_sign', val)}
+ collection={collection}
+ />
+
+
+
+
+ handleFieldChange('max_body_size', val)}
+ collection={collection}
+ />
+
+
+ );
+};
+
+export default EdgeGridAuth;
diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js
index c19ae9873..bd2c23d94 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js
@@ -12,6 +12,7 @@ import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/acti
import StyledWrapper from './StyledWrapper';
import OAuth2 from './OAuth2';
import NTLMAuth from './NTLMAuth';
+import EdgeGridAuth from './EdgeGridAuth';
const Auth = ({ collection }) => {
@@ -46,6 +47,9 @@ const Auth = ({ collection }) => {
case 'apikey': {
return
;
}
+ case 'edgegrid': {
+ return
;
+ }
}
};
diff --git a/packages/bruno-app/src/components/FolderSettings/Auth/index.js b/packages/bruno-app/src/components/FolderSettings/Auth/index.js
index 0bb8a1c37..93f71475e 100644
--- a/packages/bruno-app/src/components/FolderSettings/Auth/index.js
+++ b/packages/bruno-app/src/components/FolderSettings/Auth/index.js
@@ -17,6 +17,7 @@ import NTLMAuth from 'components/RequestPane/Auth/NTLMAuth';
import WsseAuth from 'components/RequestPane/Auth/WsseAuth';
import ApiKeyAuth from 'components/RequestPane/Auth/ApiKeyAuth';
import AwsV4Auth from 'components/RequestPane/Auth/AwsV4Auth';
+import EdgeGridAuth from 'components/RequestPane/Auth/EdgeGridAuth';
import { humanizeRequestAuthMode, getTreePathFromCollectionToItem } from 'utils/collections/index';
const GrantTypeComponentMap = ({ collection, folder }) => {
@@ -164,6 +165,17 @@ const Auth = ({ collection, folder }) => {
/>
);
}
+ case 'edgegrid': {
+ return (
+
handleSave()}
+ />
+ );
+ }
case 'oauth2': {
return (
<>
diff --git a/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js b/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js
index 36377973a..bf631163c 100644
--- a/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js
+++ b/packages/bruno-app/src/components/FolderSettings/AuthMode/index.js
@@ -107,6 +107,15 @@ const AuthMode = ({ collection, folder }) => {
>
API Key
+ {
+ dropdownTippyRef.current.hide();
+ onModeChange('edgegrid');
+ }}
+ >
+ Akamai EdgeGrid
+
{
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
index 1e3bedc2f..266fae574 100644
--- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js
@@ -106,6 +106,15 @@ const AuthMode = ({ item, collection }) => {
>
API Key
+ {
+ dropdownTippyRef?.current?.hide();
+ onModeChange('edgegrid');
+ }}
+ >
+ Akamai EdgeGrid
+
{
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/EdgeGridAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/EdgeGridAuth/StyledWrapper.js
new file mode 100644
index 000000000..d66055ca9
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/EdgeGridAuth/StyledWrapper.js
@@ -0,0 +1,20 @@
+import styled from 'styled-components';
+
+const StyledWrapper = styled.div`
+ label {
+ font-size: 0.8125rem;
+ }
+
+ .single-line-editor-wrapper {
+ padding: 0.5rem 0;
+ border-radius: 3px;
+ border: solid 1px ${(props) => props.theme.input.border};
+ background-color: ${(props) => props.theme.input.bg};
+
+ &:focus-within {
+ border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
+ }
+ }
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/EdgeGridAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/EdgeGridAuth/index.js
new file mode 100644
index 000000000..817e61dff
--- /dev/null
+++ b/packages/bruno-app/src/components/RequestPane/Auth/EdgeGridAuth/index.js
@@ -0,0 +1,167 @@
+import React from 'react';
+import SensitiveFieldWarning from 'components/SensitiveFieldWarning';
+import { useDetectSensitiveField } from 'hooks/useDetectSensitiveField';
+import get from 'lodash/get';
+import { useTheme } from 'providers/Theme';
+import { useDispatch } from 'react-redux';
+import SingleLineEditor from 'components/SingleLineEditor';
+import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
+import StyledWrapper from './StyledWrapper';
+
+const EdgeGridAuth = ({ item, collection, updateAuth, request, save }) => {
+ const dispatch = useDispatch();
+ const { storedTheme } = useTheme();
+
+ const edgeGridAuth = get(request, 'auth.edgegrid', {});
+ const { isSensitive } = useDetectSensitiveField(collection);
+ const { showWarning: showClientSecretWarning, warningMessage: clientSecretWarningMessage } = isSensitive(edgeGridAuth?.client_secret);
+
+ const handleRun = () => dispatch(sendRequest(item, collection.uid));
+
+ const handleSave = () => {
+ save();
+ };
+
+ const handleFieldChange = (field, value) => {
+ dispatch(updateAuth({
+ mode: 'edgegrid',
+ collectionUid: collection.uid,
+ itemUid: item.uid,
+ content: {
+ access_token: edgeGridAuth.access_token || '',
+ client_token: edgeGridAuth.client_token || '',
+ client_secret: edgeGridAuth.client_secret || '',
+ nonce: edgeGridAuth.nonce || '',
+ timestamp: edgeGridAuth.timestamp || '',
+ base_url: edgeGridAuth.base_url || '',
+ headers_to_sign: edgeGridAuth.headers_to_sign || '',
+ max_body_size: edgeGridAuth.max_body_size || '',
+ [field]: value || ''
+ }
+ }));
+ };
+
+ return (
+
+
+
+ handleFieldChange('access_token', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+
+
+ handleFieldChange('client_token', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+
+
+ handleFieldChange('client_secret', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ isSecret={true}
+ />
+ {showClientSecretWarning && }
+
+
+
+
+ handleFieldChange('base_url', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+
+
+ handleFieldChange('nonce', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+
+
+ handleFieldChange('timestamp', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+
+
+ handleFieldChange('headers_to_sign', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+
+
+ handleFieldChange('max_body_size', val)}
+ onRun={handleRun}
+ collection={collection}
+ item={item}
+ />
+
+
+ );
+};
+
+export default EdgeGridAuth;
diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js
index 7725f07d1..2317e963d 100644
--- a/packages/bruno-app/src/components/RequestPane/Auth/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js
@@ -12,6 +12,7 @@ import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
import ApiKeyAuth from './ApiKeyAuth';
+import EdgeGridAuth from './EdgeGridAuth';
import StyledWrapper from './StyledWrapper';
import { humanizeRequestAuthMode } from 'utils/collections';
import OAuth2 from './OAuth2/index';
@@ -96,6 +97,9 @@ const Auth = ({ item, collection }) => {
case 'apikey': {
return
;
}
+ case 'edgegrid': {
+ return
;
+ }
case 'inherit': {
const source = getEffectiveAuthSource();
return (
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index 11a9b9207..6ebe50d23 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -836,6 +836,10 @@ export const collectionsSlice = createSlice({
item.draft.request.auth.mode = 'apikey';
item.draft.request.auth.apikey = action.payload.content;
break;
+ case 'edgegrid':
+ item.draft.request.auth.mode = 'edgegrid';
+ item.draft.request.auth.edgegrid = action.payload.content;
+ break;
}
}
}
@@ -1831,6 +1835,9 @@ export const collectionsSlice = createSlice({
case 'apikey':
set(collection, 'root.request.auth.apikey', action.payload.content);
break;
+ case 'edgegrid':
+ set(collection, 'root.request.auth.edgegrid', action.payload.content);
+ break;
}
}
},
@@ -2020,6 +2027,9 @@ export const collectionsSlice = createSlice({
case 'apikey':
set(folder, 'root.request.auth.apikey', action.payload.content);
break;
+ case 'edgegrid':
+ set(folder, 'root.request.auth.edgegrid', action.payload.content);
+ break;
case 'awsv4':
set(folder, 'root.request.auth.awsv4', action.payload.content);
break;
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index 3b8440ba1..3a581fbb8 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -799,6 +799,10 @@ export const humanizeRequestAuthMode = (mode) => {
label = 'API Key';
break;
}
+ case 'edgegrid': {
+ label = 'Akamai EdgeGrid';
+ break;
+ }
}
return label;
diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js
index 89fc1dbd0..96fa6777b 100644
--- a/packages/bruno-cli/src/runner/prepare-request.js
+++ b/packages/bruno-cli/src/runner/prepare-request.js
@@ -282,6 +282,19 @@ const prepareRequest = async (item = {}, collection = {}) => {
}
}
}
+
+ if (request.auth.mode === 'edgegrid') {
+ axiosRequest.edgeGridConfig = {
+ accessToken: get(request, 'auth.edgegrid.access_token'),
+ clientToken: get(request, 'auth.edgegrid.client_token'),
+ clientSecret: get(request, 'auth.edgegrid.client_secret'),
+ nonce: get(request, 'auth.edgegrid.nonce'),
+ timestamp: get(request, 'auth.edgegrid.timestamp'),
+ baseURL: get(request, 'auth.edgegrid.base_url'),
+ headersToSign: get(request, 'auth.edgegrid.headers_to_sign'),
+ maxBodySize: get(request, 'auth.edgegrid.max_body_size')
+ };
+ }
}
request.body = request.body || {};
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index 1fff7de2b..57b13147c 100644
--- a/packages/bruno-electron/src/ipc/network/index.js
+++ b/packages/bruno-electron/src/ipc/network/index.js
@@ -13,7 +13,7 @@ const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@use
const { encodeUrl } = require('@usebruno/common').utils;
const { interpolateString } = require('./interpolate-string');
const { resolveAwsV4Credentials, addAwsV4Interceptor } = require('./awsv4auth-helper');
-const { addDigestInterceptor } = require('@usebruno/requests');
+const { addDigestInterceptor, addEdgeGridInterceptor } = require('@usebruno/requests');
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
const { prepareRequest } = require('./prepare-request');
const interpolateVars = require('./interpolate-vars');
@@ -207,6 +207,11 @@ const configureRequest = async (
addDigestInterceptor(axiosInstance, request);
}
+ if (request.edgeGridConfig) {
+ addEdgeGridInterceptor(axiosInstance, request);
+ delete request.edgeGridConfig;
+ }
+
// Get timeout from request settings, fallback to global preference
const resolvedSettings = resolveInheritedSettings(request.settings || {});
request.timeout = resolvedSettings.timeout;
diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js
index 514c7b92e..94e9bb524 100644
--- a/packages/bruno-electron/src/ipc/network/prepare-request.js
+++ b/packages/bruno-electron/src/ipc/network/prepare-request.js
@@ -71,6 +71,18 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
}
break;
+ case 'edgegrid':
+ axiosRequest.edgeGridConfig = {
+ accessToken: get(collectionAuth, 'edgegrid.access_token'),
+ clientToken: get(collectionAuth, 'edgegrid.client_token'),
+ clientSecret: get(collectionAuth, 'edgegrid.client_secret'),
+ nonce: get(collectionAuth, 'edgegrid.nonce'),
+ timestamp: get(collectionAuth, 'edgegrid.timestamp'),
+ baseURL: get(collectionAuth, 'edgegrid.base_url'),
+ headersToSign: get(collectionAuth, 'edgegrid.headers_to_sign'),
+ maxBodySize: get(collectionAuth, 'edgegrid.max_body_size')
+ };
+ break;
case 'oauth2':
const grantType = get(collectionAuth, 'oauth2.grantType');
switch (grantType) {
@@ -296,6 +308,18 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
axiosRequest.apiKeyAuthValueForQueryParams = apiKeyAuth;
}
break;
+ case 'edgegrid':
+ axiosRequest.edgeGridConfig = {
+ accessToken: get(request, 'auth.edgegrid.access_token'),
+ clientToken: get(request, 'auth.edgegrid.client_token'),
+ clientSecret: get(request, 'auth.edgegrid.client_secret'),
+ nonce: get(request, 'auth.edgegrid.nonce'),
+ timestamp: get(request, 'auth.edgegrid.timestamp'),
+ baseURL: get(request, 'auth.edgegrid.base_url'),
+ headersToSign: get(request, 'auth.edgegrid.headers_to_sign'),
+ maxBodySize: get(request, 'auth.edgegrid.max_body_size')
+ };
+ break;
}
}
diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js
index e80daa170..7a84b0c9a 100644
--- a/packages/bruno-lang/v2/src/bruToJson.js
+++ b/packages/bruno-lang/v2/src/bruToJson.js
@@ -30,7 +30,7 @@ const { safeParseJson, outdentString } = require('./utils');
*/
const grammar = ohm.grammar(`Bru {
BruFile = (meta | http | grpc | ws | query | params | headers | metadata | auths | bodies | varsandassert | script | tests | settings | docs)*
- auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth2 | authwsse | authapikey | authOauth2Configs
+ auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth2 | authwsse | authapikey | authedgegrid | authOauth2Configs
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body | bodygrpc | bodyws
bodyforms = bodyformurlencoded | bodymultipart | bodyfile
params = paramspath | paramsquery
@@ -121,6 +121,7 @@ const grammar = ohm.grammar(`Bru {
authOAuth2 = "auth:oauth2" dictionary
authwsse = "auth:wsse" dictionary
authapikey = "auth:apikey" dictionary
+ authedgegrid = "auth:edgegrid" dictionary
oauth2AuthReqHeaders = "auth:oauth2:additional_params:auth_req:headers" dictionary
oauth2AuthReqQueryParams = "auth:oauth2:additional_params:auth_req:queryparams" dictionary
@@ -856,6 +857,38 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
};
},
+ authedgegrid(_1, dictionary) {
+ const auth = mapPairListToKeyValPairs(dictionary.ast, false);
+
+ const findValueByName = (name) => {
+ const item = _.find(auth, { name });
+ return item ? item.value : '';
+ };
+
+ const access_token = findValueByName('access_token');
+ const client_token = findValueByName('client_token');
+ const client_secret = findValueByName('client_secret');
+ const nonce = findValueByName('nonce');
+ const timestamp = findValueByName('timestamp');
+ const base_url = findValueByName('base_url');
+ const headers_to_sign = findValueByName('headers_to_sign');
+ const max_body_size = findValueByName('max_body_size');
+
+ return {
+ auth: {
+ edgegrid: {
+ access_token,
+ client_token,
+ client_secret,
+ nonce,
+ timestamp,
+ base_url,
+ headers_to_sign,
+ max_body_size
+ }
+ }
+ };
+ },
bodyformurlencoded(_1, dictionary) {
return {
body: {
diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js
index f3925ad62..61466ae69 100644
--- a/packages/bruno-lang/v2/src/collectionBruToJson.js
+++ b/packages/bruno-lang/v2/src/collectionBruToJson.js
@@ -4,7 +4,7 @@ const { safeParseJson, outdentString } = require('./utils');
const grammar = ohm.grammar(`Bru {
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
- auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM |authOAuth2 | authwsse | authapikey | authOauth2Configs
+ auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM |authOAuth2 | authwsse | authapikey | authedgegrid | authOauth2Configs
// Oauth2 additional parameters
authOauth2Configs = oauth2AuthReqConfig | oauth2AccessTokenReqConfig | oauth2RefreshTokenReqConfig
@@ -61,6 +61,7 @@ const grammar = ohm.grammar(`Bru {
authOAuth2 = "auth:oauth2" dictionary
authwsse = "auth:wsse" dictionary
authapikey = "auth:apikey" dictionary
+ authedgegrid = "auth:edgegrid" dictionary
script = scriptreq | scriptres
scriptreq = "script:pre-request" st* "{" nl* textblock tagend
@@ -454,6 +455,38 @@ const sem = grammar.createSemantics().addAttribute('ast', {
}
};
},
+ authedgegrid(_1, dictionary) {
+ const auth = mapPairListToKeyValPairs(dictionary.ast, false);
+
+ const findValueByName = (name) => {
+ const item = _.find(auth, { name });
+ return item ? item.value : '';
+ };
+
+ const access_token = findValueByName('access_token');
+ const client_token = findValueByName('client_token');
+ const client_secret = findValueByName('client_secret');
+ const nonce = findValueByName('nonce');
+ const timestamp = findValueByName('timestamp');
+ const base_url = findValueByName('base_url');
+ const headers_to_sign = findValueByName('headers_to_sign');
+ const max_body_size = findValueByName('max_body_size');
+
+ return {
+ auth: {
+ edgegrid: {
+ access_token,
+ client_token,
+ client_secret,
+ nonce,
+ timestamp,
+ base_url,
+ headers_to_sign,
+ max_body_size
+ }
+ }
+ };
+ },
varsreq(_1, dictionary) {
const vars = mapPairListToKeyValPairs(dictionary.ast);
_.each(vars, (v) => {
diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js
index 9606324ff..e33606e60 100644
--- a/packages/bruno-lang/v2/src/jsonToBru.js
+++ b/packages/bruno-lang/v2/src/jsonToBru.js
@@ -468,6 +468,21 @@ ${indentString(`value: ${auth?.apikey?.value || ''}`)}
${indentString(`placement: ${auth?.apikey?.placement || ''}`)}
}
+`;
+ }
+
+ if (auth && auth.edgegrid) {
+ bru += `auth:edgegrid {
+${indentString(`access_token: ${auth?.edgegrid?.access_token || ''}`)}
+${indentString(`client_token: ${auth?.edgegrid?.client_token || ''}`)}
+${indentString(`client_secret: ${auth?.edgegrid?.client_secret || ''}`)}
+${indentString(`nonce: ${auth?.edgegrid?.nonce || ''}`)}
+${indentString(`timestamp: ${auth?.edgegrid?.timestamp || ''}`)}
+${indentString(`base_url: ${auth?.edgegrid?.base_url || ''}`)}
+${indentString(`headers_to_sign: ${auth?.edgegrid?.headers_to_sign || ''}`)}
+${indentString(`max_body_size: ${auth?.edgegrid?.max_body_size || ''}`)}
+}
+
`;
}
diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
index d5aa1c1e0..f78cc4818 100644
--- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js
+++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js
@@ -140,6 +140,21 @@ ${indentString(`key: ${auth?.apikey?.key || ''}`)}
${indentString(`value: ${auth?.apikey?.value || ''}`)}
${indentString(`placement: ${auth?.apikey?.placement || ''}`)}
}
+`;
+ }
+
+ if (auth && auth.edgegrid) {
+ bru += `auth:edgegrid {
+${indentString(`access_token: ${auth?.edgegrid?.access_token || ''}`)}
+${indentString(`client_token: ${auth?.edgegrid?.client_token || ''}`)}
+${indentString(`client_secret: ${auth?.edgegrid?.client_secret || ''}`)}
+${indentString(`nonce: ${auth?.edgegrid?.nonce || ''}`)}
+${indentString(`timestamp: ${auth?.edgegrid?.timestamp || ''}`)}
+${indentString(`base_url: ${auth?.edgegrid?.base_url || ''}`)}
+${indentString(`headers_to_sign: ${auth?.edgegrid?.headers_to_sign || ''}`)}
+${indentString(`max_body_size: ${auth?.edgegrid?.max_body_size || ''}`)}
+}
+
`;
}
diff --git a/packages/bruno-requests/src/auth/edgegrid-helper.js b/packages/bruno-requests/src/auth/edgegrid-helper.js
new file mode 100644
index 000000000..f9ba622ff
--- /dev/null
+++ b/packages/bruno-requests/src/auth/edgegrid-helper.js
@@ -0,0 +1,235 @@
+const crypto = require('crypto');
+const { URL } = require('node:url');
+
+/**
+ * Akamai EdgeGrid Authentication Helper
+ * Based on the Akamai EdgeGrid authentication specification
+ * https://techdocs.akamai.com/developer/docs/authenticate-with-edgegrid
+ */
+
+function isStrPresent(str) {
+ return str && str.trim() !== '' && str.trim() !== 'undefined';
+}
+
+/**
+ * Generate a timestamp in ISO 8601 basic format
+ * @returns {string} Timestamp in format: YYYYMMDDTHHmmss+0000
+ */
+function makeEdgeGridTimestamp() {
+ return new Date().toISOString().replace(/[:\-]|\.\d{3}/g, '');
+}
+
+/**
+ * Generate a random nonce (UUID v4)
+ * @returns {string} UUID v4 string
+ */
+function makeEdgeGridNonce() {
+ return crypto.randomUUID();
+}
+
+/**
+ * Create HMAC-SHA256 signature
+ * @param {string} data - Data to sign
+ * @param {string} key - Secret key for signing
+ * @returns {Buffer} HMAC signature
+ */
+function hmacSha256(data, key) {
+ return crypto.createHmac('sha256', key).update(data).digest();
+}
+
+/**
+ * Create base64-encoded SHA256 hash
+ * @param {string} data - Data to hash
+ * @returns {string} Base64-encoded hash
+ */
+function base64Sha256(data) {
+ return crypto.createHash('sha256').update(data).digest('base64');
+}
+
+/**
+ * Create signing key from client secret and timestamp
+ * @param {string} clientSecret - Client secret
+ * @param {string} timestamp - EdgeGrid timestamp
+ * @returns {Buffer} Signing key
+ */
+function makeSigningKey(clientSecret, timestamp) {
+ return hmacSha256(timestamp, clientSecret);
+}
+
+/**
+ * Create the data to be signed
+ * @param {Object} params
+ * @param {string} params.method - HTTP method
+ * @param {string} params.url - Request URL
+ * @param {string} params.headers - Headers to sign
+ * @param {string} params.body - Request body
+ * @param {number} params.maxBodySize - Maximum body size to sign
+ * @returns {string} Data string to be signed
+ */
+function makeDataToSign({ method, url, headers, body, maxBodySize = 131072 }) {
+ const parsedUrl = new URL(url);
+
+ // Get relative path with query string
+ const relativePath = parsedUrl.pathname + parsedUrl.search;
+
+ // Construct the canonical request (tab-separated)
+ let dataToSign = [
+ method.toUpperCase(),
+ parsedUrl.protocol.replace(':', ''),
+ parsedUrl.host,
+ relativePath
+ ].join('\t') + '\t';
+
+ // Add canonicalized headers if specified
+ if (headers && headers.trim().length > 0) {
+ dataToSign += headers.trim() + '\t';
+ } else {
+ dataToSign += '\t';
+ }
+
+ // Add body hash if present and within size limit
+ if (body && body.length > 0) {
+ const bodyToSign = body.length > maxBodySize ? body.substring(0, maxBodySize) : body;
+ dataToSign += base64Sha256(bodyToSign);
+ }
+
+ return dataToSign;
+}
+
+/**
+ * Create the authorization header value
+ * @param {Object} params
+ * @param {string} params.clientToken - Client token
+ * @param {string} params.accessToken - Access token
+ * @param {string} params.timestamp - EdgeGrid timestamp
+ * @param {string} params.nonce - Nonce value
+ * @param {string} params.signature - Request signature
+ * @returns {string} Authorization header value
+ */
+function makeAuthorizationHeader({ clientToken, accessToken, timestamp, nonce, signature }) {
+ return `EG1-HMAC-SHA256 client_token=${clientToken};access_token=${accessToken};timestamp=${timestamp};nonce=${nonce};signature=${signature}`;
+}
+
+/**
+ * Sign an EdgeGrid request
+ * @param {Object} config - EdgeGrid configuration
+ * @param {string} config.accessToken - Access token
+ * @param {string} config.clientToken - Client token
+ * @param {string} config.clientSecret - Client secret
+ * @param {string} [config.baseURL] - Base URL for the API endpoint
+ * @param {string} [config.nonce] - Optional nonce override
+ * @param {string} [config.timestamp] - Optional timestamp override
+ * @param {string} [config.headersToSign] - Headers to include in signature
+ * @param {number} [config.maxBodySize=131072] - Maximum body size to sign (default 128KB)
+ * @param {Object} request - Axios request config
+ * @returns {string} Authorization header value
+ */
+export function signEdgeGridRequest(config, request) {
+ const { accessToken, clientToken, clientSecret, baseURL, headersToSign } = config;
+ // Ensure maxBodySize is a number, default to 128KB if not provided or invalid
+ const maxBodySize = config.maxBodySize ? parseInt(config.maxBodySize, 10) : 131072;
+
+ // Validate required fields
+ if (!isStrPresent(accessToken)) {
+ throw new Error('EdgeGrid: accessToken is required');
+ }
+ if (!isStrPresent(clientToken)) {
+ throw new Error('EdgeGrid: clientToken is required');
+ }
+ if (!isStrPresent(clientSecret)) {
+ throw new Error('EdgeGrid: clientSecret is required');
+ }
+
+ // Generate or use provided nonce and timestamp
+ const nonce = config.nonce && isStrPresent(config.nonce) ? config.nonce : makeEdgeGridNonce();
+ const timestamp = config.timestamp && isStrPresent(config.timestamp) ? config.timestamp : makeEdgeGridTimestamp();
+
+ // Create signing key
+ const signingKey = makeSigningKey(clientSecret, timestamp);
+
+ // Prepare request body
+ let bodyString = '';
+ if (request.data) {
+ if (typeof request.data === 'string') {
+ // If it's a string, try to parse and re-stringify to ensure compact JSON
+ try {
+ const parsed = JSON.parse(request.data);
+ bodyString = JSON.stringify(parsed); // Compact JSON, no spaces
+ } catch (e) {
+ // If not valid JSON, use as-is
+ bodyString = request.data;
+ }
+ } else if (typeof request.data === 'object') {
+ // Serialize to compact JSON (no spaces/newlines)
+ bodyString = JSON.stringify(request.data);
+ }
+ }
+
+ // Determine URL to sign - use baseURL if provided, otherwise use request URL
+ let urlToSign = request.url;
+ if (baseURL && isStrPresent(baseURL)) {
+ // Parse the request URL to get the path and query
+ const requestUrl = new URL(request.url);
+ const baseParsed = new URL(baseURL);
+ // Construct URL using baseURL's protocol and host with request's path
+ urlToSign = `${baseParsed.protocol}//${baseParsed.host}${requestUrl.pathname}${requestUrl.search}`;
+ }
+
+ // Create data to sign
+ const dataToSign = makeDataToSign({
+ method: request.method,
+ url: urlToSign,
+ headers: headersToSign || '',
+ body: bodyString,
+ maxBodySize
+ });
+
+ // Create the auth data string (without the EG1-HMAC-SHA256 prefix)
+ const authData = [
+ `client_token=${clientToken}`,
+ `access_token=${accessToken}`,
+ `timestamp=${timestamp}`,
+ `nonce=${nonce}`
+ ].join(';') + ';';
+
+ // Sign the auth data + data to sign
+ const signatureData = authData + dataToSign;
+ const signature = hmacSha256(signatureData, signingKey).toString('base64');
+
+ // Return complete authorization header
+ return makeAuthorizationHeader({
+ clientToken,
+ accessToken,
+ timestamp,
+ nonce,
+ signature
+ });
+}
+
+/**
+ * Add EdgeGrid interceptor to axios instance
+ * @param {Object} axiosInstance - Axios instance
+ * @param {Object} request - Request object with edgeGridConfig
+ */
+export function addEdgeGridInterceptor(axiosInstance, request) {
+ const { edgeGridConfig } = request;
+
+ if (!edgeGridConfig) {
+ return;
+ }
+
+ // Add request interceptor to sign requests
+ axiosInstance.interceptors.request.use((config) => {
+ try {
+ const authHeader = signEdgeGridRequest(edgeGridConfig, config);
+ config.headers['Authorization'] = authHeader;
+ return config;
+ } catch (error) {
+ console.error('EdgeGrid signing error:', error);
+ return Promise.reject(error);
+ }
+ },
+ (error) => {
+ return Promise.reject(error);
+ });
+}
diff --git a/packages/bruno-requests/src/auth/index.ts b/packages/bruno-requests/src/auth/index.ts
index 082ca796b..6e8c19a3b 100644
--- a/packages/bruno-requests/src/auth/index.ts
+++ b/packages/bruno-requests/src/auth/index.ts
@@ -1,2 +1,3 @@
export { addDigestInterceptor } from './digestauth-helper';
-export { getOAuth2Token } from './oauth2-helper';
\ No newline at end of file
+export { getOAuth2Token } from './oauth2-helper';
+export { addEdgeGridInterceptor, signEdgeGridRequest } from './edgegrid-helper';
diff --git a/packages/bruno-requests/src/index.ts b/packages/bruno-requests/src/index.ts
index c30e41772..baef59b28 100644
--- a/packages/bruno-requests/src/index.ts
+++ b/packages/bruno-requests/src/index.ts
@@ -1,4 +1,4 @@
-export { addDigestInterceptor, getOAuth2Token } from './auth';
+export { addDigestInterceptor, getOAuth2Token, addEdgeGridInterceptor } from './auth';
export { GrpcClient, generateGrpcSampleMessage } from './grpc';
export { WsClient } from './ws/ws-client';
export { default as cookies } from './cookies';
diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js
index 10852a3da..ef0ae054f 100644
--- a/packages/bruno-schema/src/collections/index.js
+++ b/packages/bruno-schema/src/collections/index.js
@@ -158,6 +158,19 @@ const authApiKeySchema = Yup.object({
.noUnknown(true)
.strict();
+const authEdgeGridSchema = Yup.object({
+ access_token: Yup.string().nullable(),
+ client_token: Yup.string().nullable(),
+ client_secret: Yup.string().nullable(),
+ nonce: Yup.string().nullable(),
+ timestamp: Yup.string().nullable(),
+ base_url: Yup.string().nullable(),
+ headers_to_sign: Yup.string().nullable(),
+ max_body_size: Yup.string().nullable()
+})
+ .noUnknown(true)
+ .strict();
+
const oauth2AuthorizationAdditionalParametersSchema = Yup.object({
name: Yup.string().nullable(),
value: Yup.string().nullable(),
@@ -291,7 +304,7 @@ const oauth2Schema = Yup.object({
const authSchema = Yup.object({
mode: Yup.string()
- .oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'ntlm', 'oauth2', 'wsse', 'apikey'])
+ .oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'ntlm', 'oauth2', 'wsse', 'apikey', 'edgegrid'])
.required('mode is required'),
awsv4: authAwsV4Schema.nullable(),
basic: authBasicSchema.nullable(),
@@ -300,7 +313,8 @@ const authSchema = Yup.object({
digest: authDigestSchema.nullable(),
oauth2: oauth2Schema.nullable(),
wsse: authWsseSchema.nullable(),
- apikey: authApiKeySchema.nullable()
+ apikey: authApiKeySchema.nullable(),
+ edgegrid: authEdgeGridSchema.nullable()
})
.noUnknown(true)
.strict()