mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-22 20:25:38 +00:00
add: akamai edgegrid auth
This commit is contained in:
committed by
_Pragadesh M
parent
e47d1ed353
commit
baaf4570c4
@@ -106,6 +106,15 @@ const AuthMode = ({ collection }) => {
|
||||
>
|
||||
API Key
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('edgegrid');
|
||||
}}
|
||||
>
|
||||
Akamai EdgeGrid
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Access Token</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.access_token || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('access_token', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Client Token</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.client_token || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('client_token', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Client Secret</label>
|
||||
<div className="single-line-editor-wrapper mb-2 flex items-center">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.client_secret || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('client_secret', val)}
|
||||
collection={collection}
|
||||
isSecret={true}
|
||||
/>
|
||||
{showClientSecretWarning && <SensitiveFieldWarning fieldName="edgegrid-client-secret" warningMessage={clientSecretWarningMessage} />}
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Base URL</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.base_url || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('base_url', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Nonce
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, auto-generated if empty)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.nonce || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('nonce', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Timestamp
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, auto-generated if empty)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.timestamp || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('timestamp', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Headers to Sign
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, comma-separated)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.headers_to_sign || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('headers_to_sign', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Max Body Size
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, in bytes, default: 131072)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.max_body_size || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('max_body_size', val)}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EdgeGridAuth;
|
||||
@@ -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 <ApiKeyAuth collection={collection} />;
|
||||
}
|
||||
case 'edgegrid': {
|
||||
return <EdgeGridAuth collection={collection} />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<EdgeGridAuth
|
||||
collection={collection}
|
||||
item={folder}
|
||||
updateAuth={updateFolderAuth}
|
||||
request={request}
|
||||
save={() => handleSave()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'oauth2': {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -107,6 +107,15 @@ const AuthMode = ({ collection, folder }) => {
|
||||
>
|
||||
API Key
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onModeChange('edgegrid');
|
||||
}}
|
||||
>
|
||||
Akamai EdgeGrid
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -106,6 +106,15 @@ const AuthMode = ({ item, collection }) => {
|
||||
>
|
||||
API Key
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('edgegrid');
|
||||
}}
|
||||
>
|
||||
Akamai EdgeGrid
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
|
||||
@@ -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;
|
||||
@@ -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 (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Access Token</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.access_token || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('access_token', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Client Token</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.client_token || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('client_token', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Client Secret</label>
|
||||
<div className="single-line-editor-wrapper mb-2 flex items-center">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.client_secret || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('client_secret', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
isSecret={true}
|
||||
/>
|
||||
{showClientSecretWarning && <SensitiveFieldWarning fieldName="edgegrid-client-secret" warningMessage={clientSecretWarningMessage} />}
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Base URL</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.base_url || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('base_url', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Nonce
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, auto-generated if empty)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.nonce || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('nonce', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Timestamp
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, auto-generated if empty)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.timestamp || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('timestamp', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Headers to Sign
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, comma-separated)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.headers_to_sign || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('headers_to_sign', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">
|
||||
Max Body Size
|
||||
<span className="text-xs text-gray-500 ml-2">(optional, in bytes, default: 131072)</span>
|
||||
</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={edgeGridAuth.max_body_size || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleFieldChange('max_body_size', val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default EdgeGridAuth;
|
||||
@@ -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 <ApiKeyAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
|
||||
}
|
||||
case 'edgegrid': {
|
||||
return <EdgeGridAuth collection={collection} item={item} request={request} save={save} updateAuth={updateAuth} />;
|
||||
}
|
||||
case 'inherit': {
|
||||
const source = getEffectiveAuthSource();
|
||||
return (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -799,6 +799,10 @@ export const humanizeRequestAuthMode = (mode) => {
|
||||
label = 'API Key';
|
||||
break;
|
||||
}
|
||||
case 'edgegrid': {
|
||||
label = 'Akamai EdgeGrid';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
|
||||
@@ -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 || {};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
235
packages/bruno-requests/src/auth/edgegrid-helper.js
Normal file
235
packages/bruno-requests/src/auth/edgegrid-helper.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export { addDigestInterceptor } from './digestauth-helper';
|
||||
export { getOAuth2Token } from './oauth2-helper';
|
||||
export { getOAuth2Token } from './oauth2-helper';
|
||||
export { addEdgeGridInterceptor, signEdgeGridRequest } from './edgegrid-helper';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user