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 2e0abe572..5cf3a5f7a 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js @@ -34,6 +34,15 @@ const AuthMode = ({ collection }) => {
} placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + onModeChange('awsv4'); + }} + > + AWS Sig v4 +
{ diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/StyledWrapper.js new file mode 100644 index 000000000..c2bb5d207 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/StyledWrapper.js @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js new file mode 100644 index 000000000..1fe35eea0 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AwsV4Auth/index.js @@ -0,0 +1,192 @@ +import React from 'react'; +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 AwsV4Auth = ({ collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const awsv4Auth = get(collection, 'root.request.auth.awsv4', {}); + console.log('saved auth', awsv4Auth); + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const handleAccessKeyIdChange = (accessKeyId) => { + dispatch( + updateCollectionAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + content: { + accessKeyId: accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleSecretAccessKeyChange = (secretAccessKey) => { + dispatch( + updateCollectionAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleSessionTokenChange = (sessionToken) => { + dispatch( + updateCollectionAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleServiceChange = (service) => { + dispatch( + updateCollectionAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleRegionChange = (region) => { + dispatch( + updateCollectionAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleProfileNameChange = (profileName) => { + dispatch( + updateCollectionAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: profileName + } + }) + ); + }; + + return ( + + +
+ handleAccessKeyIdChange(val)} + collection={collection} + /> +
+ + +
+ handleSecretAccessKeyChange(val)} + collection={collection} + /> +
+ + +
+ handleSessionTokenChange(val)} + collection={collection} + /> +
+ + +
+ handleServiceChange(val)} + collection={collection} + /> +
+ + +
+ handleRegionChange(val)} + collection={collection} + /> +
+ + +
+ handleProfileNameChange(val)} + collection={collection} + /> +
+
+ ); +}; + +export default AwsV4Auth; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js index fe2fd5b33..d9e80358b 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js @@ -2,6 +2,7 @@ import React from 'react'; import get from 'lodash/get'; import { useDispatch } from 'react-redux'; import AuthMode from './AuthMode'; +import AwsV4Auth from './AwsV4Auth'; import BearerAuth from './BearerAuth'; import BasicAuth from './BasicAuth'; import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; @@ -15,6 +16,9 @@ const Auth = ({ collection }) => { const getAuthView = () => { switch (authMode) { + case 'awsv4': { + return ; + } case 'basic': { return ; } 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 43c90e2ce..10130c3a9 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js @@ -35,6 +35,15 @@ const AuthMode = ({ item, collection }) => {
} placement="bottom-end"> +
{ + dropdownTippyRef.current.hide(); + onModeChange('awsv4'); + }} + > + AWS Sig v4 +
{ diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/StyledWrapper.js new file mode 100644 index 000000000..c2bb5d207 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/StyledWrapper.js @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js new file mode 100644 index 000000000..9ed29ac07 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/AwsV4Auth/index.js @@ -0,0 +1,206 @@ +import React, { useState } from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; +import { update } from 'lodash'; + +const AwsV4Auth = ({ onTokenChange, item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const awsv4Auth = item.draft ? get(item, 'draft.request.auth.awsv4', {}) : get(item, 'request.auth.awsv4', {}); + console.log('saved auth', awsv4Auth); + + const handleRun = () => dispatch(sendRequest(item, collection.uid)); + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleAccessKeyIdChange = (accessKeyId) => { + dispatch( + updateAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + accessKeyId: accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleSecretAccessKeyChange = (secretAccessKey) => { + dispatch( + updateAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleSessionTokenChange = (sessionToken) => { + dispatch( + updateAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleServiceChange = (service) => { + dispatch( + updateAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: service, + region: awsv4Auth.region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleRegionChange = (region) => { + dispatch( + updateAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: region, + profileName: awsv4Auth.profileName + } + }) + ); + }; + + const handleProfileNameChange = (profileName) => { + dispatch( + updateAuth({ + mode: 'awsv4', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + accessKeyId: awsv4Auth.accessKeyId, + secretAccessKey: awsv4Auth.secretAccessKey, + sessionToken: awsv4Auth.sessionToken, + service: awsv4Auth.service, + region: awsv4Auth.region, + profileName: profileName + } + }) + ); + }; + + return ( + + +
+ handleAccessKeyIdChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handleSecretAccessKeyChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handleSessionTokenChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handleServiceChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handleRegionChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handleProfileNameChange(val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); +}; + +export default AwsV4Auth; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js index 314bbb212..669f4a843 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -1,6 +1,7 @@ import React from 'react'; import get from 'lodash/get'; import AuthMode from './AuthMode'; +import AwsV4Auth from './AwsV4Auth'; import BearerAuth from './BearerAuth'; import BasicAuth from './BasicAuth'; import StyledWrapper from './StyledWrapper'; @@ -10,6 +11,9 @@ const Auth = ({ item, collection }) => { const getAuthView = () => { switch (authMode) { + case 'awsv4': { + return ; + } case 'basic': { 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 1e9465594..8140dcf72 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -379,6 +379,10 @@ export const collectionsSlice = createSlice({ item.draft.request.auth = item.draft.request.auth || {}; switch (action.payload.mode) { + case 'awsv4': + item.draft.request.auth.mode = 'awsv4'; + item.draft.request.auth.awsv4 = action.payload.content; + break; case 'bearer': item.draft.request.auth.mode = 'bearer'; item.draft.request.auth.bearer = action.payload.content; @@ -965,6 +969,10 @@ export const collectionsSlice = createSlice({ if (collection) { switch (action.payload.mode) { + case 'awsv4': + set(collection, 'root.request.auth.awsv4', action.payload.content); + console.log('set auth awsv4', action.payload.content); + break; case 'bearer': set(collection, 'root.request.auth.bearer', 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 5998fd9b7..e4532b7e0 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -481,6 +481,10 @@ export const humanizeRequestBodyMode = (mode) => { export const humanizeRequestAuthMode = (mode) => { let label = 'No Auth'; switch (mode) { + case 'awsv4': { + label = 'AWS Sig V4'; + break; + } case 'basic': { label = 'Basic Auth'; break; diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index cedd5df60..ba6cc1bf4 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -15,10 +15,12 @@ "test": "jest" }, "dependencies": { + "@aws-sdk/credential-providers": "^3.425.0", "@usebruno/js": "0.8.0", "@usebruno/lang": "0.8.0", "@usebruno/schema": "0.5.0", "about-window": "^1.15.2", + "aws4-axios": "^3.3.0", "axios": "^1.5.1", "chai": "^4.3.7", "chai-string": "^1.5.0", diff --git a/packages/bruno-electron/src/ipc/network/awsv4auth-helper.js b/packages/bruno-electron/src/ipc/network/awsv4auth-helper.js new file mode 100644 index 000000000..e3c990624 --- /dev/null +++ b/packages/bruno-electron/src/ipc/network/awsv4auth-helper.js @@ -0,0 +1,56 @@ +const { fromIni } = require('@aws-sdk/credential-providers'); +const { aws4Interceptor } = require('aws4-axios'); + +function isStrPresent(str) { + return str && str !== '' && str !== 'undefined'; +} + +async function resolveCredentials(request) { + const awsv4 = request.awsv4config; + if (isStrPresent(awsv4.profileName)) { + try { + credentialsProvider = fromIni({ + profile: awsv4.profileName + }); + credentials = await credentialsProvider(); + awsv4.accessKeyId = credentials.accessKeyId; + awsv4.secretAccessKey = credentials.secretAccessKey; + awsv4.sessionToken = credentials.sessionToken; + } catch { + console.error('Failed to fetch credentials from AWS profile.'); + } + } + return awsv4; +} + +function addAwsV4Interceptor(axiosInstance, request) { + if (!request.awsv4config) { + console.warn('No Auth Config found!'); + return; + } + + const awsv4 = request.awsv4config; + if (!isStrPresent(awsv4.accessKeyId) || !isStrPresent(awsv4.secretAccessKey)) { + console.warn('Required Auth Fields are not present'); + return; + } + + const interceptor = aws4Interceptor({ + options: { + region: awsv4.region, + service: awsv4.service + }, + credentials: { + accessKeyId: awsv4.accessKeyId, + secretAccessKey: awsv4.secretAccessKey, + sessionToken: awsv4.sessionToken + } + }); + + axiosInstance.interceptors.request.use(interceptor); +} + +module.exports = { + addAwsV4Interceptor, + resolveCredentials +}; diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index da7c14e64..1def8d964 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -22,6 +22,7 @@ const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('./axios-instance'); +const { addAwsV4Interceptor, resolveCredentials } = require('./awsv4auth-helper'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -270,6 +271,12 @@ const registerNetworkIpc = (mainWindow) => { const axiosInstance = makeAxiosInstance(); + if (request.awsv4config) { + request.awsv4config = await resolveCredentials(request); + addAwsV4Interceptor(axiosInstance, request); + delete request.awsv4config; + } + /** @type {import('axios').AxiosResponse} */ const response = await axiosInstance(request); diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index b92616478..31d5cab55 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -121,6 +121,16 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces delete request.auth; } + // interpolate vars for aws sigv4 auth + if (request.awsv4config) { + request.awsv4config.accessKeyId = interpolate(request.awsv4config.accessKeyId) || ''; + request.awsv4config.secretAccessKey = interpolate(request.awsv4config.secretAccessKey) || ''; + request.awsv4config.sessionToken = interpolate(request.awsv4config.sessionToken) || ''; + request.awsv4config.service = interpolate(request.awsv4config.service) || ''; + request.awsv4config.region = interpolate(request.awsv4config.region) || ''; + request.awsv4config.profileName = interpolate(request.awsv4config.profileName) || ''; + } + return request; }; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 44f408ca9..3beab80f7 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -8,28 +8,50 @@ const decomment = require('decomment'); const setAuthHeaders = (axiosRequest, request, collectionRoot) => { const collectionAuth = get(collectionRoot, 'request.auth'); if (collectionAuth) { - if (collectionAuth.mode === 'basic') { - axiosRequest.auth = { - username: get(collectionAuth, 'basic.username'), - password: get(collectionAuth, 'basic.password') - }; - } - - if (collectionAuth.mode === 'bearer') { - axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; + switch (collectionAuth.mode) { + case 'awsv4': + axiosRequest.awsv4config = { + accessKeyId: get(collectionAuth, 'awsv4.accessKeyId'), + secretAccessKey: get(collectionAuth, 'awsv4.secretAccessKey'), + sessionToken: get(collectionAuth, 'awsv4.sessionToken'), + service: get(collectionAuth, 'awsv4.service'), + region: get(collectionAuth, 'awsv4.region'), + profileName: get(collectionAuth, 'awsv4.profileName') + }; + break; + case 'basic': + axiosRequest.auth = { + username: get(collectionAuth, 'basic.username'), + password: get(collectionAuth, 'basic.password') + }; + break; + case 'bearer': + axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; + break; } } if (request.auth) { - if (request.auth.mode === 'basic') { - axiosRequest.auth = { - username: get(request, 'auth.basic.username'), - password: get(request, 'auth.basic.password') - }; - } - - if (request.auth.mode === 'bearer') { - axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; + switch (request.auth.mode) { + case 'awsv4': + axiosRequest.awsv4config = { + accessKeyId: get(request, 'auth.awsv4.accessKeyId'), + secretAccessKey: get(request, 'auth.awsv4.secretAccessKey'), + sessionToken: get(request, 'auth.awsv4.sessionToken'), + service: get(request, 'auth.awsv4.service'), + region: get(request, 'auth.awsv4.region'), + profileName: get(request, 'auth.awsv4.profileName') + }; + break; + case 'basic': + axiosRequest.auth = { + username: get(request, 'auth.basic.username'), + password: get(request, 'auth.basic.password') + }; + break; + case 'bearer': + axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; + break; } } diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index b8b23e8fb..5ce65eba0 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -23,7 +23,7 @@ const { outdentString } = require('../../v1/src/utils'); */ const grammar = ohm.grammar(`Bru { BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)* - auths = authbasic | authbearer + auths = authawsv4 | authbasic | authbearer bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body bodyforms = bodyformurlencoded | bodymultipart @@ -76,6 +76,7 @@ const grammar = ohm.grammar(`Bru { varsres = "vars:post-response" dictionary assert = "assert" assertdictionary + authawsv4 = "auth:awsv4" dictionary authbasic = "auth:basic" dictionary authbearer = "auth:bearer" dictionary @@ -295,6 +296,33 @@ const sem = grammar.createSemantics().addAttribute('ast', { headers: mapPairListToKeyValPairs(dictionary.ast) }; }, + authawsv4(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const accessKeyIdKey = _.find(auth, { name: 'accessKeyId' }); + const secretAccessKeyKey = _.find(auth, { name: 'secretAccessKey' }); + const sessionTokenKey = _.find(auth, { name: 'sessionToken' }); + const serviceKey = _.find(auth, { name: 'service' }); + const regionKey = _.find(auth, { name: 'region' }); + const profileNameKey = _.find(auth, { name: 'profileName' }); + const accessKeyId = accessKeyIdKey ? accessKeyIdKey.value : ''; + const secretAccessKey = secretAccessKeyKey ? secretAccessKeyKey.value : ''; + const sessionToken = sessionTokenKey ? sessionTokenKey.value : ''; + const service = serviceKey ? serviceKey.value : ''; + const region = regionKey ? regionKey.value : ''; + const profileName = profileNameKey ? profileNameKey.value : ''; + return { + auth: { + awsv4: { + accessKeyId, + secretAccessKey, + sessionToken, + service, + region, + profileName + } + } + }; + }, authbasic(_1, dictionary) { const auth = mapPairListToKeyValPairs(dictionary.ast, false); const usernameKey = _.find(auth, { name: 'username' }); diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js index d78f752c0..4569736f1 100644 --- a/packages/bruno-lang/v2/src/collectionBruToJson.js +++ b/packages/bruno-lang/v2/src/collectionBruToJson.js @@ -4,7 +4,7 @@ const { outdentString } = require('../../v1/src/utils'); const grammar = ohm.grammar(`Bru { BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)* - auths = authbasic | authbearer + auths = authawsv4 | authbasic | authbearer nl = "\\r"? "\\n" st = " " | "\\t" @@ -38,6 +38,7 @@ const grammar = ohm.grammar(`Bru { varsreq = "vars:pre-request" dictionary varsres = "vars:post-response" dictionary + authawsv4 = "auth:awsv4" dictionary authbasic = "auth:basic" dictionary authbearer = "auth:bearer" dictionary @@ -171,6 +172,33 @@ const sem = grammar.createSemantics().addAttribute('ast', { headers: mapPairListToKeyValPairs(dictionary.ast) }; }, + authawsv4(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const accessKeyIdKey = _.find(auth, { name: 'accessKeyId' }); + const secretAccessKeyKey = _.find(auth, { name: 'secretAccessKey' }); + const sessionTokenKey = _.find(auth, { name: 'sessionToken' }); + const serviceKey = _.find(auth, { name: 'service' }); + const regionKey = _.find(auth, { name: 'region' }); + const profileNameKey = _.find(auth, { name: 'profileName' }); + const accessKeyId = accessKeyIdKey ? accessKeyIdKey.value : ''; + const secretAccessKey = secretAccessKeyKey ? secretAccessKeyKey.value : ''; + const sessionToken = sessionTokenKey ? sessionTokenKey.value : ''; + const service = serviceKey ? serviceKey.value : ''; + const region = regionKey ? regionKey.value : ''; + const profileName = profileNameKey ? profileNameKey.value : ''; + return { + auth: { + awsv4: { + accessKeyId, + secretAccessKey, + sessionToken, + service, + region, + profileName + } + } + }; + }, authbasic(_1, dictionary) { const auth = mapPairListToKeyValPairs(dictionary.ast, false); const usernameKey = _.find(auth, { name: 'username' }); diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index d1f174579..757406b70 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -87,6 +87,19 @@ const jsonToBru = (json) => { bru += '\n}\n\n'; } + if (auth && auth.awsv4) { + bru += `auth:awsv4 { +${indentString(`accessKeyId: ${auth.awsv4.accessKeyId}`)} +${indentString(`secretAccessKey: ${auth.awsv4.secretAccessKey}`)} +${indentString(`sessionToken: ${auth.awsv4.sessionToken}`)} +${indentString(`service: ${auth.awsv4.service}`)} +${indentString(`region: ${auth.awsv4.region}`)} +${indentString(`profileName: ${auth.awsv4.profileName}`)} +} + +`; + } + if (auth && auth.basic) { bru += `auth:basic { ${indentString(`username: ${auth.basic.username}`)} diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js index 57a5ea7bf..ea928a68f 100644 --- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js +++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js @@ -72,6 +72,19 @@ const jsonToCollectionBru = (json) => { ${indentString(`mode: ${auth.mode}`)} } +`; + } + + if (auth && auth.awsv4) { + bru += `auth:awsv4 { +${indentString(`accessKeyId: ${auth.awsv4.accessKeyId}`)} +${indentString(`secretAccessKey: ${auth.awsv4.secretAccessKey}`)} +${indentString(`sessionToken: ${auth.awsv4.sessionToken}`)} +${indentString(`service: ${auth.awsv4.service}`)} +${indentString(`region: ${auth.awsv4.region}`)} +${indentString(`profileName: ${auth.awsv4.profileName}`)} +} + `; } diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru index af556f3e8..22168b194 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.bru +++ b/packages/bruno-lang/v2/tests/fixtures/request.bru @@ -22,6 +22,15 @@ headers { ~transaction-id: {{transactionId}} } +auth:awsv4 { + accessKeyId: A12345678 + secretAccessKey: thisisasecret + sessionToken: thisisafakesessiontoken + service: execute-api + region: us-east-1 + profileName: test_profile +} + auth:basic { username: john password: secret diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json index e8b3d8cd4..def7b5f08 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.json +++ b/packages/bruno-lang/v2/tests/fixtures/request.json @@ -45,6 +45,14 @@ } ], "auth": { + "awsv4": { + "accessKeyId": "A12345678", + "secretAccessKey": "thisisasecret", + "sessionToken": "thisisafakesessiontoken", + "service": "execute-api", + "region": "us-east-1", + "profileName": "test_profile" + }, "basic": { "username": "john", "password": "secret" diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index b73364b7d..1721d386a 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -70,6 +70,17 @@ const requestBodySchema = Yup.object({ .noUnknown(true) .strict(); +const authAwsV4Schema = Yup.object({ + accessKeyId: Yup.string().nullable(), + secretAccessKey: Yup.string().nullable(), + sessionToken: Yup.string().nullable(), + service: Yup.string().nullable(), + region: Yup.string().nullable(), + profileName: Yup.string().nullable() +}) + .noUnknown(true) + .strict(); + const authBasicSchema = Yup.object({ username: Yup.string().nullable(), password: Yup.string().nullable() @@ -84,7 +95,8 @@ const authBearerSchema = Yup.object({ .strict(); const authSchema = Yup.object({ - mode: Yup.string().oneOf(['none', 'basic', 'bearer']).required('mode is required'), + mode: Yup.string().oneOf(['none', 'awsv4', 'basic', 'bearer']).required('mode is required'), + awsv4: authAwsV4Schema.nullable(), basic: authBasicSchema.nullable(), bearer: authBearerSchema.nullable() })