mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-03 01:18:32 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e85b302b8 | ||
|
|
3b51621580 | ||
|
|
18e7301550 | ||
|
|
96bcc7074a | ||
|
|
f64e13a71f | ||
|
|
01360d1522 | ||
|
|
6729d718cf | ||
|
|
17abc19770 | ||
|
|
13cb71eaef | ||
|
|
b375620875 | ||
|
|
1cf8a2f3f1 | ||
|
|
7c416a99ef | ||
|
|
389a383b99 | ||
|
|
d1a8f59c79 | ||
|
|
d477cfc7e1 | ||
|
|
e66e26d115 | ||
|
|
7a635810b1 | ||
|
|
dde6695a43 | ||
|
|
9f81e6dc73 | ||
|
|
3c2cbe63c4 | ||
|
|
a4b13d5c2a | ||
|
|
064281d438 | ||
|
|
43c873422f | ||
|
|
c7c762185e | ||
|
|
09c496e516 | ||
|
|
117726a01f | ||
|
|
e2d754702a | ||
|
|
fee3416c85 | ||
|
|
a756c49285 | ||
|
|
b6abc665a5 | ||
|
|
bd002ca316 | ||
|
|
5fece08f4b | ||
|
|
36b7fbe584 |
12
.github/workflows/bump-homebrew-cask.yml
vendored
12
.github/workflows/bump-homebrew-cask.yml
vendored
@@ -1,12 +0,0 @@
|
||||
name: Bump Homebrew Cask
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- name: Bump Homebrew Cask
|
||||
run: brew bump-cask-pr bruno --version "${GITHUB_REF_NAME#v}"
|
||||
48
.github/workflows/npm-bru-cli.yml
vendored
Normal file
48
.github/workflows/npm-bru-cli.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Bru CLI Tests (npm)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build:
|
||||
description: 'Test Bru CLI (npm)'
|
||||
required: true
|
||||
default: 'true'
|
||||
|
||||
# Assign permissions for unit tests to be reported.
|
||||
# See https://github.com/dorny/test-reporter/issues/168
|
||||
permissions:
|
||||
statuses: write
|
||||
checks: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: CLI Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install Bru CLI from NPM
|
||||
run: npm install -g @usebruno/cli
|
||||
|
||||
- name: Display Bru CLI Version
|
||||
run: bru --version
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd packages/bruno-tests/collection
|
||||
npm install
|
||||
bru run --env Prod --output junit.xml --format junit
|
||||
|
||||
- name: Publish Test Report
|
||||
uses: dorny/test-reporter@v1
|
||||
if: success() || failure()
|
||||
with:
|
||||
name: Test Report
|
||||
path: packages/bruno-tests/collection/junit.xml
|
||||
reporter: java-junit
|
||||
27
.github/workflows/playwright.yml
vendored
27
.github/workflows/playwright.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm i --legacy-peer-deps
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npm run test:e2e
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
1193
package-lock.json
generated
1193
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,7 @@
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"platform": "^1.3.6",
|
||||
"posthog-node": "^2.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"qs": "^6.11.0",
|
||||
"query-string": "^7.0.1",
|
||||
"react": "18.2.0",
|
||||
@@ -70,7 +71,6 @@
|
||||
"strip-json-comments": "^5.0.1",
|
||||
"styled-components": "^5.3.3",
|
||||
"system": "^2.0.1",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"url": "^0.11.3",
|
||||
"xml-formatter": "^3.5.0",
|
||||
"yargs-parser": "^21.1.1",
|
||||
@@ -82,6 +82,7 @@
|
||||
"@babel/preset-env": "^7.16.4",
|
||||
"@babel/preset-react": "^7.16.0",
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^8.2.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.5.1",
|
||||
@@ -89,8 +90,9 @@
|
||||
"html-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"prettier": "^2.7.1",
|
||||
"postcss": "^8.4.35",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
}
|
||||
|
||||
6
packages/bruno-app/postcss.config.js
Normal file
6
packages/bruno-app/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
@@ -11,7 +11,7 @@ const BearerAuth = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const bearerToken = get(collection, 'root.request.auth.bearer.token');
|
||||
const bearerToken = get(collection, 'root.request.auth.bearer.token', '');
|
||||
|
||||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Docs = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const docs = get(collection, 'root.docs', '');
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
@@ -40,7 +40,7 @@ const Docs = ({ collection }) => {
|
||||
{isEditing ? (
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
value={docs || ''}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
|
||||
@@ -12,7 +12,7 @@ const Script = ({ collection }) => {
|
||||
const requestScript = get(collection, 'root.request.script.req', '');
|
||||
const responseScript = get(collection, 'root.request.script.res', '');
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const onRequestScriptEdit = (value) => {
|
||||
@@ -44,7 +44,7 @@ const Script = ({ collection }) => {
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
@@ -56,7 +56,7 @@ const Script = ({ collection }) => {
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
|
||||
@@ -11,7 +11,7 @@ const Tests = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const tests = get(collection, 'root.request.tests', '');
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const onEdit = (value) => {
|
||||
@@ -30,7 +30,7 @@ const Tests = ({ collection }) => {
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={tests || ''}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
|
||||
@@ -11,7 +11,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Documentation = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const docs = item.draft ? get(item, 'draft.request.docs') : get(item, 'request.docs');
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
@@ -45,7 +45,7 @@ const Documentation = ({ item, collection }) => {
|
||||
{isEditing ? (
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
value={docs || ''}
|
||||
onEdit={onEdit}
|
||||
|
||||
@@ -4,7 +4,6 @@ const Wrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-weight: 600;
|
||||
table-layout: fixed;
|
||||
|
||||
thead,
|
||||
@@ -16,6 +15,7 @@ const Wrapper = styled.div`
|
||||
color: ${(props) => props.theme.table.thead.color};
|
||||
font-size: 0.8125rem;
|
||||
user-select: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
|
||||
@@ -38,7 +38,7 @@ const AuthMode = ({ item, collection }) => {
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('awsv4');
|
||||
}}
|
||||
>
|
||||
@@ -47,7 +47,7 @@ const AuthMode = ({ item, collection }) => {
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('basic');
|
||||
}}
|
||||
>
|
||||
@@ -56,7 +56,7 @@ const AuthMode = ({ item, collection }) => {
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('bearer');
|
||||
}}
|
||||
>
|
||||
@@ -65,7 +65,7 @@ const AuthMode = ({ item, collection }) => {
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('digest');
|
||||
}}
|
||||
>
|
||||
@@ -74,7 +74,25 @@ const AuthMode = ({ item, collection }) => {
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('oauth2');
|
||||
}}
|
||||
>
|
||||
OAuth 2.0
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('inherit');
|
||||
}}
|
||||
>
|
||||
Inherit
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef?.current?.hide();
|
||||
onModeChange('none');
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -6,6 +6,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
|
||||
@@ -6,6 +6,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
|
||||
@@ -6,6 +6,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
|
||||
@@ -12,8 +12,8 @@ const BearerAuth = ({ item, collection }) => {
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const bearerToken = item.draft
|
||||
? get(item, 'draft.request.auth.bearer.token')
|
||||
: get(item, 'request.auth.bearer.token');
|
||||
? get(item, 'draft.request.auth.bearer.token', '')
|
||||
: get(item, 'request.auth.bearer.token', '');
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
@@ -6,6 +6,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
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;
|
||||
@@ -0,0 +1,65 @@
|
||||
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 { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { inputsConfig } from './inputsConfig';
|
||||
|
||||
const OAuth2AuthorizationCode = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
|
||||
const handleRun = async () => {
|
||||
dispatch(sendRequest(item, collection.uid));
|
||||
};
|
||||
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleChange = (key, value) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'authorization_code',
|
||||
...oAuth,
|
||||
[key]: value
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 flex w-full gap-4 flex-col">
|
||||
{inputsConfig.map((input) => {
|
||||
const { key, label } = input;
|
||||
return (
|
||||
<div className="flex flex-col w-full gap-1" key={`input-${key}`}>
|
||||
<label className="block font-medium">{label}</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={oAuth[key] || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleChange(key, val)}
|
||||
onRun={() => {}}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
|
||||
Get Access Token
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2AuthorizationCode;
|
||||
@@ -0,0 +1,28 @@
|
||||
const inputsConfig = [
|
||||
{
|
||||
key: 'callbackUrl',
|
||||
label: 'Callback URL'
|
||||
},
|
||||
{
|
||||
key: 'authorizationUrl',
|
||||
label: 'Auth URL'
|
||||
},
|
||||
{
|
||||
key: 'accessTokenUrl',
|
||||
label: 'Access Token URL'
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID'
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret'
|
||||
},
|
||||
{
|
||||
key: 'scope',
|
||||
label: 'Scope'
|
||||
}
|
||||
];
|
||||
|
||||
export { inputsConfig };
|
||||
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
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;
|
||||
@@ -0,0 +1,78 @@
|
||||
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 { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const OAuth2ClientCredentials = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleClientIdChange = (clientId) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'client_credentials',
|
||||
clientId: clientId,
|
||||
clientSecret: oAuth.clientSecret
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleClientSecretChange = (clientSecret) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'client_credentials',
|
||||
clientId: oAuth.clientId,
|
||||
clientSecret: clientSecret
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Client Id</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={oAuth.clientId || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleClientIdChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Client Secret</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={oAuth.clientSecret || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleClientSecretChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2ClientCredentials;
|
||||
@@ -0,0 +1,54 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: 0.8125rem;
|
||||
|
||||
.grant-type-mode-selector {
|
||||
padding: 0.5rem 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px ${(props) => props.theme.input.border};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
|
||||
.dropdown {
|
||||
width: fit-content;
|
||||
|
||||
div[data-tippy-root] {
|
||||
width: fit-content;
|
||||
}
|
||||
.tippy-box {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
|
||||
.tippy-content: {
|
||||
width: fit-content;
|
||||
max-width: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grant-type-label {
|
||||
width: fit-content;
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
justify-content: space-between;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
|
||||
.label-item {
|
||||
padding: 0.2rem 0.6rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.caret {
|
||||
color: rgb(140, 140, 140);
|
||||
fill: rgb(140 140 140);
|
||||
}
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useRef, forwardRef } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { IconCaretDown } from '@tabler/icons';
|
||||
import { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { humanizeGrantType } from 'utils/collections';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const GrantTypeSelector = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dropdownTippyRef = useRef();
|
||||
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
|
||||
const Icon = forwardRef((props, ref) => {
|
||||
return (
|
||||
<div ref={ref} className="flex items-center justify-end grant-type-label select-none">
|
||||
{humanizeGrantType(oAuth?.grantType)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const onGrantTypeChange = (grantType) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// initalize redux state with a default oauth2 auth type
|
||||
// authorization_code - default option
|
||||
!oAuth?.grantType &&
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'authorization_code'
|
||||
}
|
||||
})
|
||||
);
|
||||
}, [oAuth]);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<label className="block font-medium mb-2">Grant Type</label>
|
||||
<div className="inline-flex items-center cursor-pointer grant-type-mode-selector w-fit">
|
||||
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onGrantTypeChange('password');
|
||||
}}
|
||||
>
|
||||
Resource Owner Password Credentials
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onGrantTypeChange('authorization_code');
|
||||
}}
|
||||
>
|
||||
Authorization Code
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-item"
|
||||
onClick={() => {
|
||||
dropdownTippyRef.current.hide();
|
||||
onGrantTypeChange('client_credentials');
|
||||
}}
|
||||
>
|
||||
Client Credentials
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export default GrantTypeSelector;
|
||||
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
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;
|
||||
@@ -0,0 +1,78 @@
|
||||
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 { updateAuth } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const OAuth2Ropc = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
|
||||
const handleRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
const handleUsernameChange = (username) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'password',
|
||||
username: username,
|
||||
password: oAuth.password
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (password) => {
|
||||
dispatch(
|
||||
updateAuth({
|
||||
mode: 'oauth2',
|
||||
collectionUid: collection.uid,
|
||||
itemUid: item.uid,
|
||||
content: {
|
||||
grantType: 'password',
|
||||
username: oAuth.username,
|
||||
password: password
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<label className="block font-medium mb-2">Username</label>
|
||||
<div className="single-line-editor-wrapper mb-2">
|
||||
<SingleLineEditor
|
||||
value={oAuth.username || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handleUsernameChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="block font-medium mb-2">Password</label>
|
||||
<div className="single-line-editor-wrapper">
|
||||
<SingleLineEditor
|
||||
value={oAuth.password || ''}
|
||||
theme={storedTheme}
|
||||
onSave={handleSave}
|
||||
onChange={(val) => handlePasswordChange(val)}
|
||||
onRun={handleRun}
|
||||
collection={collection}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2Ropc;
|
||||
@@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.single-line-editor-wrapper {
|
||||
max-width: 400px;
|
||||
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;
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import GrantTypeSelector from './GrantTypeSelector/index';
|
||||
import OAuth2Ropc from './Ropc/index';
|
||||
import OAuth2AuthorizationCode from './AuthorizationCode/index';
|
||||
import OAuth2ClientCredentials from './ClientCredentials/index';
|
||||
|
||||
const grantTypeComponentMap = (grantType, item, collection) => {
|
||||
switch (grantType) {
|
||||
case 'password':
|
||||
return <OAuth2Ropc item={item} collection={collection} />;
|
||||
break;
|
||||
case 'authorization_code':
|
||||
return <OAuth2AuthorizationCode item={item} collection={collection} />;
|
||||
break;
|
||||
case 'client_credentials':
|
||||
return <OAuth2ClientCredentials item={item} collection={collection} />;
|
||||
break;
|
||||
default:
|
||||
return <div>TBD</div>;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const OAuth2 = ({ item, collection }) => {
|
||||
const oAuth = item.draft ? get(item, 'draft.request.auth.oauth2', {}) : get(item, 'request.auth.oauth2', {});
|
||||
|
||||
return (
|
||||
<StyledWrapper className="mt-2 w-full">
|
||||
<GrantTypeSelector item={item} collection={collection} />
|
||||
{grantTypeComponentMap(oAuth?.grantType, item, collection)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default OAuth2;
|
||||
@@ -1,5 +1,11 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div``;
|
||||
const Wrapper = styled.div`
|
||||
.inherit-mode-text {
|
||||
color: ${(props) => props.theme.colors.text.yellow};
|
||||
}
|
||||
.inherit-mode-label {
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -6,10 +6,15 @@ import BearerAuth from './BearerAuth';
|
||||
import BasicAuth from './BasicAuth';
|
||||
import DigestAuth from './DigestAuth';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { humanizeRequestAuthMode } from 'utils/collections/index';
|
||||
import OAuth2 from './OAuth2/index';
|
||||
|
||||
const Auth = ({ item, collection }) => {
|
||||
const authMode = item.draft ? get(item, 'draft.request.auth.mode') : get(item, 'request.auth.mode');
|
||||
|
||||
const collectionRoot = get(collection, 'root', {});
|
||||
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||
|
||||
const getAuthView = () => {
|
||||
switch (authMode) {
|
||||
case 'awsv4': {
|
||||
@@ -24,6 +29,17 @@ const Auth = ({ item, collection }) => {
|
||||
case 'digest': {
|
||||
return <DigestAuth collection={collection} item={item} />;
|
||||
}
|
||||
case 'oauth2': {
|
||||
return <OAuth2 collection={collection} item={item} />;
|
||||
}
|
||||
case 'inherit': {
|
||||
return (
|
||||
<div className="flex flex-row w-full mt-2 gap-2">
|
||||
<div>Auth inherited from the Collection: </div>
|
||||
<div className="inherit-mode-text">{humanizeRequestAuthMode(collectionAuth?.mode)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,4 +52,5 @@ const Auth = ({ item, collection }) => {
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Auth;
|
||||
|
||||
@@ -10,7 +10,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const onEdit = (value) => {
|
||||
@@ -31,7 +31,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={variables || ''}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
import React from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import MD from 'markdown-it';
|
||||
import { format } from 'prettier/standalone';
|
||||
import prettierPluginGraphql from 'prettier/parser-graphql';
|
||||
import { getAllVariables } from 'utils/collections';
|
||||
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
|
||||
import toast from 'react-hot-toast';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { IconWand } from '@tabler/icons';
|
||||
|
||||
import onHasCompletion from './onHasCompletion';
|
||||
|
||||
@@ -178,6 +182,19 @@ export default class QueryEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
beautifyRequestBody = () => {
|
||||
try {
|
||||
const prettyQuery = format(this.props.value, {
|
||||
parser: 'graphql',
|
||||
plugins: [prettierPluginGraphql]
|
||||
});
|
||||
|
||||
this.editor.setValue(prettyQuery);
|
||||
} catch (e) {
|
||||
toast.error('Error occurred while prettifying GraphQL query');
|
||||
}
|
||||
};
|
||||
|
||||
// Todo: Overlay is messing up with schema hint
|
||||
// Fix this
|
||||
addOverlay = () => {
|
||||
@@ -189,13 +206,23 @@ export default class QueryEditor extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledWrapper
|
||||
className="h-full w-full"
|
||||
aria-label="Query Editor"
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<StyledWrapper
|
||||
className="h-full w-full relative"
|
||||
aria-label="Query Editor"
|
||||
ref={(node) => {
|
||||
this._node = node;
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="btn-add-param text-link px-4 py-4 select-none absolute top-0 right-0 z-10"
|
||||
onClick={this.beautifyRequestBody}
|
||||
title="prettify"
|
||||
>
|
||||
<IconWand size={20} strokeWidth={1.5} />
|
||||
</button>
|
||||
</StyledWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const RequestBody = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
|
||||
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const onEdit = (value) => {
|
||||
@@ -48,7 +48,7 @@ const RequestBody = ({ item, collection }) => {
|
||||
<StyledWrapper className="w-full">
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
value={bodyContent[bodyMode] || ''}
|
||||
onEdit={onEdit}
|
||||
|
||||
@@ -12,7 +12,7 @@ const Script = ({ item, collection }) => {
|
||||
const requestScript = item.draft ? get(item, 'draft.request.script.req') : get(item, 'request.script.req');
|
||||
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const onRequestScriptEdit = (value) => {
|
||||
@@ -45,7 +45,7 @@ const Script = ({ item, collection }) => {
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
@@ -58,7 +58,7 @@ const Script = ({ item, collection }) => {
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
|
||||
@@ -11,7 +11,7 @@ const Tests = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
const onEdit = (value) => {
|
||||
@@ -32,7 +32,7 @@ const Tests = ({ item, collection }) => {
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={tests || ''}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
onEdit={onEdit}
|
||||
mode="javascript"
|
||||
|
||||
@@ -28,6 +28,19 @@ const RequestTab = ({ tab, collection }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleMouseUp = (e) => {
|
||||
if (e.button === 1) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
dispatch(
|
||||
closeTabs({
|
||||
tabUids: [tab.uid]
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getMethodColor = (method = '') => {
|
||||
const theme = storedTheme === 'dark' ? darkTheme : lightTheme;
|
||||
|
||||
@@ -124,7 +137,18 @@ const RequestTab = ({ tab, collection }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-baseline tab-label pl-2">
|
||||
<div
|
||||
className="flex items-baseline tab-label pl-2"
|
||||
onMouseUp={(e) => {
|
||||
if (!item.draft) return handleMouseUp(e);
|
||||
|
||||
if (e.button === 1) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowConfirmClose(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
|
||||
{method}
|
||||
</span>
|
||||
|
||||
@@ -19,7 +19,7 @@ const QueryResultPreview = ({
|
||||
collection,
|
||||
mode,
|
||||
disableRunEventListener,
|
||||
storedTheme
|
||||
displayedTheme
|
||||
}) => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const dispatch = useDispatch();
|
||||
@@ -71,7 +71,7 @@ const QueryResultPreview = ({
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
onRun={onRun}
|
||||
value={formattedData}
|
||||
mode={mode}
|
||||
|
||||
@@ -51,7 +51,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
||||
const mode = getCodeMirrorModeBasedOnContentType(contentType, data);
|
||||
const [filter, setFilter] = useState(null);
|
||||
const formattedData = formatResponse(data, mode, filter);
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
|
||||
const debouncedResultFilterOnChange = debounce((e) => {
|
||||
setFilter(e.target.value);
|
||||
@@ -132,7 +132,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
||||
collection={collection}
|
||||
allowedPreviewModes={allowedPreviewModes}
|
||||
disableRunEventListener={disableRunEventListener}
|
||||
storedTheme={storedTheme}
|
||||
displayedTheme={displayedTheme}
|
||||
/>
|
||||
{queryFilterEnabled && <QueryResultFilter onChange={debouncedResultFilterOnChange} mode={mode} />}
|
||||
</>
|
||||
|
||||
@@ -22,13 +22,23 @@ const getRelativePath = (fullPath, pathname) => {
|
||||
|
||||
export default function RunnerResults({ collection }) {
|
||||
const dispatch = useDispatch();
|
||||
const listWrapperRef = useRef();
|
||||
const [selectedItem, setSelectedItem] = useState(null);
|
||||
|
||||
// ref for the runner output body
|
||||
const runnerBodyRef = useRef();
|
||||
|
||||
const autoScrollRunnerBody = () => {
|
||||
if (runnerBodyRef?.current) {
|
||||
// mimicks the native terminal scroll style
|
||||
runnerBodyRef.current.scrollTo(0, 100000);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!collection.runnerResult) {
|
||||
setSelectedItem(null);
|
||||
}
|
||||
autoScrollRunnerBody();
|
||||
}, [collection, setSelectedItem]);
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
@@ -67,11 +77,6 @@ export default function RunnerResults({ collection }) {
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
useEffect(() => {
|
||||
if (listWrapperRef.current) {
|
||||
listWrapperRef.current.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
|
||||
}
|
||||
}, [items]);
|
||||
const runCollection = () => {
|
||||
dispatch(runCollectionFolder(collection.uid, null, true));
|
||||
};
|
||||
@@ -102,12 +107,11 @@ export default function RunnerResults({ collection }) {
|
||||
|
||||
if (!items || !items.length) {
|
||||
return (
|
||||
<StyledWrapper className="px-4">
|
||||
<StyledWrapper className="px-4 pb-4">
|
||||
<div className="font-medium mt-6 title flex items-center">
|
||||
Runner
|
||||
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
|
||||
</div>
|
||||
@@ -124,21 +128,24 @@ export default function RunnerResults({ collection }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledWrapper ref={listWrapperRef} className="px-4 pb-4 flex flex-grow flex-col relative">
|
||||
<div className="flex flex-row mt-6 mb-4">
|
||||
<div className="font-medium title flex items-center">
|
||||
<StyledWrapper className="px-4 pb-4 flex flex-grow flex-col relative">
|
||||
<div className="flex flex-row">
|
||||
<div className="font-medium my-6 title flex items-center">
|
||||
Runner
|
||||
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
|
||||
</div>
|
||||
{runnerInfo.status !== 'ended' && runnerInfo.cancelTokenUid && (
|
||||
<button className="btn ml-6 btn-sm btn-danger" onClick={cancelExecution}>
|
||||
<button className="btn ml-6 my-4 btn-sm btn-danger" onClick={cancelExecution}>
|
||||
Cancel Execution
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1">
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="py-2 font-medium test-summary">
|
||||
<div className="flex flex-row gap-4">
|
||||
<div
|
||||
className="flex flex-col flex-1 overflow-y-auto h-[calc(100vh_-_12rem)] max-h-[calc(100vh_-_12rem)] w-full"
|
||||
ref={runnerBodyRef}
|
||||
>
|
||||
<div className="pb-2 font-medium test-summary">
|
||||
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
|
||||
</div>
|
||||
{items.map((item) => {
|
||||
@@ -227,8 +234,8 @@ export default function RunnerResults({ collection }) {
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-1" style={{ width: '50%' }}>
|
||||
{selectedItem ? (
|
||||
{selectedItem ? (
|
||||
<div className="flex flex-1 w-[50%]">
|
||||
<div className="flex flex-col w-full overflow-auto">
|
||||
<div className="flex items-center px-3 mb-4 font-medium">
|
||||
<span className="mr-2">{selectedItem.relativePath}</span>
|
||||
@@ -243,8 +250,8 @@ export default function RunnerResults({ collection }) {
|
||||
{/* <div className='px-3 mb-4 font-medium'>{selectedItem.relativePath}</div> */}
|
||||
<ResponsePane item={selectedItem} collection={collection} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IconCopy } from '@tabler/icons';
|
||||
import { findCollectionByItemUid } from '../../../../../../../utils/collections/index';
|
||||
|
||||
const CodeView = ({ language, item }) => {
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const { target, client, language: lang } = language;
|
||||
const requestHeaders = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
|
||||
@@ -45,7 +45,7 @@ const CodeView = ({ language, item }) => {
|
||||
readOnly
|
||||
value={snippet}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
theme={storedTheme}
|
||||
theme={displayedTheme}
|
||||
mode={lang}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -110,7 +110,6 @@ const GoldenEdition = ({ onClose }) => {
|
||||
const handlePricingOptionChange = (option) => {
|
||||
setPricingOption(option);
|
||||
};
|
||||
console.log(displayedTheme);
|
||||
|
||||
const themeBasedContainerClassNames = displayedTheme === 'light' ? 'text-gray-900' : 'text-white';
|
||||
const themeBasedTabContainerClassNames = displayedTheme === 'light' ? 'bg-gray-200' : 'bg-gray-800';
|
||||
@@ -131,8 +130,7 @@ const GoldenEdition = ({ onClose }) => {
|
||||
target="_blank"
|
||||
className="flex text-white bg-yellow-600 hover:bg-yellow-700 font-medium rounded-lg text-sm px-4 py-2 text-center cursor-pointer"
|
||||
>
|
||||
<IconHeart size={18} strokeWidth={1.5} />{' '}
|
||||
<span className="ml-2">{pricingOption === 'individuals' ? 'Buy' : 'Subscribe'}</span>
|
||||
<IconHeart size={18} strokeWidth={1.5} /> <span className="ml-2">Buy</span>
|
||||
</a>
|
||||
</div>
|
||||
{pricingOption === 'individuals' ? (
|
||||
@@ -146,9 +144,11 @@ const GoldenEdition = ({ onClose }) => {
|
||||
) : (
|
||||
<div>
|
||||
<div className="my-4">
|
||||
<span className="text-3xl font-extrabold">$5</span>
|
||||
<span className="text-3xl font-extrabold">$49</span>
|
||||
<span className="ml-2">/ user</span>
|
||||
</div>
|
||||
<p>/user/month</p>
|
||||
<p className="bg-yellow-200 text-black rounded-md px-2 py-1 mb-2 inline-flex text-sm">One Time Payment</p>
|
||||
<p className="text-sm">perpetual license with 2 years of updates</p>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
|
||||
@@ -124,7 +124,7 @@ const Sidebar = () => {
|
||||
Star
|
||||
</GitHubButton> */}
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.9.0</div>
|
||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.10.0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,6 +159,33 @@ const GlobalStyle = createGlobalStyle`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// scrollbar styling
|
||||
// the below media query target non-macos devices
|
||||
// (macos scrollbar styling is the ideal style reference)
|
||||
@media not all and (pointer: coarse) {
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: ${(props) => props.theme.scrollbar.color};
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: ${(props) => props.theme.scrollbar.color};
|
||||
border-radius: 14px;
|
||||
border: 3px solid ${(props) => props.theme.scrollbar.color};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// codemirror
|
||||
.CodeMirror {
|
||||
.cm-variable-valid {
|
||||
|
||||
@@ -10,7 +10,6 @@ import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
import '../styles/app.scss';
|
||||
import '../styles/globals.css';
|
||||
import 'tailwindcss/dist/tailwind.min.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'graphiql/graphiql.min.css';
|
||||
import 'react-tooltip/dist/react-tooltip.css';
|
||||
|
||||
@@ -60,7 +60,7 @@ const trackStart = () => {
|
||||
event: 'start',
|
||||
properties: {
|
||||
os: platformLib.os.family,
|
||||
version: '1.9.0'
|
||||
version: '1.10.0'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -402,6 +402,10 @@ export const collectionsSlice = createSlice({
|
||||
item.draft.request.auth.mode = 'digest';
|
||||
item.draft.request.auth.digest = action.payload.content;
|
||||
break;
|
||||
case 'oauth2':
|
||||
item.draft.request.auth.mode = 'oauth2';
|
||||
item.draft.request.auth.oauth2 = action.payload.content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
:root {
|
||||
--color-brand: #546de5;
|
||||
--color-text: rgb(52 52 52);
|
||||
|
||||
@@ -238,6 +238,10 @@ const darkTheme = {
|
||||
|
||||
plainGrid: {
|
||||
hoverBg: '#3D3D3D'
|
||||
},
|
||||
|
||||
scrollbar: {
|
||||
color: 'rgb(52 51 49)'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -242,6 +242,10 @@ const lightTheme = {
|
||||
|
||||
plainGrid: {
|
||||
hoverBg: '#f4f4f4'
|
||||
},
|
||||
|
||||
scrollbar: {
|
||||
color: 'rgb(152 151 149)'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -489,6 +489,10 @@ export const humanizeRequestBodyMode = (mode) => {
|
||||
export const humanizeRequestAuthMode = (mode) => {
|
||||
let label = 'No Auth';
|
||||
switch (mode) {
|
||||
case 'inherit': {
|
||||
label = 'Inherit';
|
||||
break;
|
||||
}
|
||||
case 'awsv4': {
|
||||
label = 'AWS Sig V4';
|
||||
break;
|
||||
@@ -505,6 +509,30 @@ export const humanizeRequestAuthMode = (mode) => {
|
||||
label = 'Digest Auth';
|
||||
break;
|
||||
}
|
||||
case 'oauth2': {
|
||||
label = 'OAuth 2.0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
export const humanizeGrantType = (mode) => {
|
||||
let label = 'No Auth';
|
||||
switch (mode) {
|
||||
case 'password': {
|
||||
label = 'Resource Owner Password Credentials';
|
||||
break;
|
||||
}
|
||||
case 'authorization_code': {
|
||||
label = 'Authorization Code';
|
||||
break;
|
||||
}
|
||||
case 'client_credentials': {
|
||||
label = 'Client Credentials';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
|
||||
8
packages/bruno-app/tailwind.config.js
Normal file
8
packages/bruno-app/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@usebruno/cli",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.2",
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
@@ -24,9 +24,11 @@
|
||||
"package.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "^3.425.0",
|
||||
"@usebruno/common": "0.1.0",
|
||||
"@usebruno/js": "0.10.1",
|
||||
"@usebruno/lang": "0.10.0",
|
||||
"aws4-axios": "^3.3.0",
|
||||
"axios": "^1.5.1",
|
||||
"chai": "^4.3.7",
|
||||
"chalk": "^3.0.0",
|
||||
|
||||
56
packages/bruno-cli/src/runner/awsv4auth-helper.js
Normal file
56
packages/bruno-cli/src/runner/awsv4auth-helper.js
Normal file
@@ -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 resolveAwsV4Credentials(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,
|
||||
resolveAwsV4Credentials
|
||||
};
|
||||
@@ -31,12 +31,19 @@ const prepareRequest = (request, collectionRoot) => {
|
||||
headers: headers
|
||||
};
|
||||
|
||||
// Authentication
|
||||
// A request can override the collection auth with another auth
|
||||
// But it cannot override the collection auth with no auth
|
||||
// We will provide support for disabling the auth via scripting in the future
|
||||
/**
|
||||
* 27 Feb 2024:
|
||||
* ['inherit', 'none'].includes(request.auth.mode)
|
||||
* We are mainitaining the old behavior where 'none' used to inherit the collection auth.
|
||||
*
|
||||
* Very soon, 'none' will be treated as no auth and 'inherit' will be the only way to inherit collection auth.
|
||||
* We will request users to update their collection files to use 'inherit' instead of 'none'.
|
||||
* Don't want to break ongoing CI pipelines.
|
||||
*
|
||||
* Hoping to remove this by 1 April 2024.
|
||||
*/
|
||||
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||
if (collectionAuth) {
|
||||
if (collectionAuth && ['inherit', 'none'].includes(request.auth.mode)) {
|
||||
if (collectionAuth.mode === 'basic') {
|
||||
axiosRequest.auth = {
|
||||
username: get(collectionAuth, 'basic.username'),
|
||||
@@ -57,6 +64,17 @@ const prepareRequest = (request, collectionRoot) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (request.auth.mode === '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')
|
||||
};
|
||||
}
|
||||
|
||||
if (request.auth.mode === 'bearer') {
|
||||
axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const https = require('https');
|
||||
const { HttpProxyAgent } = require('http-proxy-agent');
|
||||
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||
const { makeAxiosInstance } = require('../utils/axios-instance');
|
||||
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
|
||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
|
||||
|
||||
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
|
||||
@@ -190,6 +191,24 @@ const runSingleRequest = async function (
|
||||
// run request
|
||||
const axiosInstance = makeAxiosInstance();
|
||||
|
||||
if (request.awsv4config) {
|
||||
// todo: make this happen in prepare-request.js
|
||||
// interpolate the aws v4 config
|
||||
request.awsv4config.accessKeyId = interpolateString(request.awsv4config.accessKeyId, interpolationOptions);
|
||||
request.awsv4config.secretAccessKey = interpolateString(
|
||||
request.awsv4config.secretAccessKey,
|
||||
interpolationOptions
|
||||
);
|
||||
request.awsv4config.sessionToken = interpolateString(request.awsv4config.sessionToken, interpolationOptions);
|
||||
request.awsv4config.service = interpolateString(request.awsv4config.service, interpolationOptions);
|
||||
request.awsv4config.region = interpolateString(request.awsv4config.region, interpolationOptions);
|
||||
request.awsv4config.profileName = interpolateString(request.awsv4config.profileName, interpolationOptions);
|
||||
|
||||
request.awsv4config = await resolveAwsV4Credentials(request);
|
||||
addAwsV4Interceptor(axiosInstance, request);
|
||||
delete request.awsv4config;
|
||||
}
|
||||
|
||||
/** @type {import('axios').AxiosResponse} */
|
||||
response = await axiosInstance(request);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "v1.9.0",
|
||||
"version": "v1.10.0",
|
||||
"name": "bruno",
|
||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||
"homepage": "https://www.usebruno.com",
|
||||
|
||||
@@ -21,7 +21,8 @@ const contentSecurityPolicy = [
|
||||
"script-src * 'unsafe-inline' 'unsafe-eval'",
|
||||
"connect-src * 'unsafe-inline'",
|
||||
"font-src 'self' https:",
|
||||
"form-action 'none'",
|
||||
// this has been commented out to make oauth2 work
|
||||
// "form-action 'none'",
|
||||
"img-src 'self' blob: data: https:",
|
||||
"style-src 'self' 'unsafe-inline' https:"
|
||||
];
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const { BrowserWindow } = require('electron');
|
||||
|
||||
const authorizeUserInWindow = ({ authorizeUrl, callbackUrl }) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let finalUrl = null;
|
||||
|
||||
const window = new BrowserWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: false
|
||||
},
|
||||
show: false
|
||||
});
|
||||
window.on('ready-to-show', window.show.bind(window));
|
||||
|
||||
function onWindowRedirect(url) {
|
||||
// check if the url contains an authorization code
|
||||
if (url.match(/(code=).*/)) {
|
||||
finalUrl = url;
|
||||
if (url && finalUrl.includes(callbackUrl)) {
|
||||
window.close();
|
||||
} else {
|
||||
reject(new Error('Invalid Callback Url'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.on('close', () => {
|
||||
if (finalUrl) {
|
||||
try {
|
||||
const callbackUrlWithCode = new URL(finalUrl);
|
||||
const authorizationCode = callbackUrlWithCode.searchParams.get('code');
|
||||
|
||||
return resolve(authorizationCode);
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
}
|
||||
} else {
|
||||
return reject(new Error('Authorization window closed'));
|
||||
}
|
||||
});
|
||||
|
||||
// wait for the window to navigate to the callback url
|
||||
const didNavigateListener = (_, url) => {
|
||||
onWindowRedirect(url);
|
||||
};
|
||||
window.webContents.on('did-navigate', didNavigateListener);
|
||||
const willRedirectListener = (_, authorizeUrl) => {
|
||||
onWindowRedirect(authorizeUrl);
|
||||
};
|
||||
window.webContents.on('will-redirect', willRedirectListener);
|
||||
|
||||
try {
|
||||
await window.loadURL(authorizeUrl);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { authorizeUserInWindow };
|
||||
@@ -9,7 +9,7 @@ const Mustache = require('mustache');
|
||||
const contentDispositionParser = require('content-disposition');
|
||||
const mime = require('mime-types');
|
||||
const { ipcMain } = require('electron');
|
||||
const { isUndefined, isNull, each, get, compact } = require('lodash');
|
||||
const { isUndefined, isNull, each, get, compact, cloneDeep } = require('lodash');
|
||||
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||
const prepareRequest = require('./prepare-request');
|
||||
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
||||
@@ -29,6 +29,7 @@ const { addDigestInterceptor } = require('./digestauth-helper');
|
||||
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util');
|
||||
const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem');
|
||||
const { getCookieStringForUrl, addCookieToJar, getDomainsWithCookies } = require('../../utils/cookies');
|
||||
const { resolveOAuth2AuthorizationCodecessToken } = require('./oauth2-authorization-code-helper');
|
||||
|
||||
// override the default escape function to prevent escaping
|
||||
Mustache.escape = function (value) {
|
||||
@@ -189,6 +190,16 @@ const configureRequest = async (
|
||||
|
||||
const axiosInstance = makeAxiosInstance();
|
||||
|
||||
if (request.oauth2) {
|
||||
if (request?.oauth2?.grantType == 'authorization_code') {
|
||||
let requestCopy = cloneDeep(request);
|
||||
interpolateVars(requestCopy, envVars, collectionVariables, processEnvVars);
|
||||
const { data, url } = await resolveOAuth2AuthorizationCodecessToken(requestCopy);
|
||||
request.data = data;
|
||||
request.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.awsv4config) {
|
||||
request.awsv4config = await resolveAwsV4Credentials(request);
|
||||
addAwsV4Interceptor(axiosInstance, request);
|
||||
@@ -484,7 +495,6 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
setCookieHeaders = Array.isArray(response.headers['set-cookie'])
|
||||
? response.headers['set-cookie']
|
||||
: [response.headers['set-cookie']];
|
||||
|
||||
for (let setCookieHeader of setCookieHeaders) {
|
||||
if (typeof setCookieHeader === 'string' && setCookieHeader.length) {
|
||||
addCookieToJar(setCookieHeader, request.url);
|
||||
@@ -495,6 +505,7 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
|
||||
// send domain cookies to renderer
|
||||
const domainsWithCookies = await getDomainsWithCookies();
|
||||
|
||||
mainWindow.webContents.send('main:cookies-update', safeParseJSON(safeStringifyJSON(domainsWithCookies)));
|
||||
|
||||
await runPostResponse(
|
||||
|
||||
@@ -98,17 +98,53 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces
|
||||
}
|
||||
|
||||
// todo: we have things happening in two places w.r.t basic auth
|
||||
// need to refactor this in the future
|
||||
// need to refactor this in the future
|
||||
// the request.auth (basic auth) object gets set inside the prepare-request.js file
|
||||
if (request.auth) {
|
||||
const username = _interpolate(request.auth.username) || '';
|
||||
const password = _interpolate(request.auth.password) || '';
|
||||
|
||||
// use auth header based approach and delete the request.auth object
|
||||
request.headers['authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
||||
delete request.auth;
|
||||
}
|
||||
|
||||
if (request?.oauth2?.grantType) {
|
||||
switch (request.oauth2.grantType) {
|
||||
case 'password':
|
||||
let username = _interpolate(request.oauth2.username) || '';
|
||||
let password = _interpolate(request.oauth2.password) || '';
|
||||
request.oauth2.username = username;
|
||||
request.oauth2.password = password;
|
||||
request.data = {
|
||||
grant_type: 'password',
|
||||
username,
|
||||
password
|
||||
};
|
||||
break;
|
||||
case 'authorization_code':
|
||||
request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || '';
|
||||
request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || '';
|
||||
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
|
||||
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
|
||||
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
||||
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
|
||||
break;
|
||||
case 'client_credentials':
|
||||
let clientId = _interpolate(request.oauth2.clientId) || '';
|
||||
let clientSecret = _interpolate(request.oauth2.clientSecret) || '';
|
||||
request.oauth2.clientId = clientId;
|
||||
request.oauth2.clientSecret = clientSecret;
|
||||
request.data = {
|
||||
grant_type: 'client_credentials',
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// interpolate vars for aws sigv4 auth
|
||||
if (request.awsv4config) {
|
||||
request.awsv4config.accessKeyId = _interpolate(request.awsv4config.accessKeyId) || '';
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
const { get, cloneDeep } = require('lodash');
|
||||
const { authorizeUserInWindow } = require('./authorize-user-in-window');
|
||||
|
||||
const resolveOAuth2AuthorizationCodecessToken = async (request) => {
|
||||
let requestCopy = cloneDeep(request);
|
||||
const authorization_code = await getOAuth2AuthorizationCode(requestCopy);
|
||||
const oAuth = get(requestCopy, 'oauth2', {});
|
||||
const { clientId, clientSecret, callbackUrl, scope } = oAuth;
|
||||
const data = {
|
||||
grant_type: 'authorization_code',
|
||||
code: authorization_code,
|
||||
redirect_uri: callbackUrl,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
scope: scope
|
||||
};
|
||||
const url = requestCopy?.oauth2?.accessTokenUrl;
|
||||
return {
|
||||
data,
|
||||
url
|
||||
};
|
||||
};
|
||||
|
||||
const getOAuth2AuthorizationCode = (request) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { oauth2 } = request;
|
||||
const { callbackUrl, clientId, authorizationUrl, scope } = oauth2;
|
||||
const authorizationUrlWithQueryParams = `${authorizationUrl}?client_id=${clientId}&redirect_uri=${callbackUrl}&response_type=code&scope=${scope}`;
|
||||
try {
|
||||
const code = await authorizeUserInWindow({ authorizeUrl: authorizationUrlWithQueryParams, callbackUrl });
|
||||
resolve(code);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
resolveOAuth2AuthorizationCodecessToken,
|
||||
getOAuth2AuthorizationCode
|
||||
};
|
||||
@@ -29,13 +29,20 @@ const parseFormData = (datas, collectionPath) => {
|
||||
return form;
|
||||
};
|
||||
|
||||
// Authentication
|
||||
// A request can override the collection auth with another auth
|
||||
// But it cannot override the collection auth with no auth
|
||||
// We will provide support for disabling the auth via scripting in the future
|
||||
/**
|
||||
* 27 Feb 2024:
|
||||
* ['inherit', 'none'].includes(request.auth.mode)
|
||||
* We are mainitaining the old behavior where 'none' used to inherit the collection auth.
|
||||
*
|
||||
* Very soon, 'none' will be treated as no auth and 'inherit' will be the only way to inherit collection auth.
|
||||
* We will request users to update their collection files to use 'inherit' instead of 'none'.
|
||||
* Don't want to break ongoing CI pipelines.
|
||||
*
|
||||
* Hoping to remove this by 1 April 2024.
|
||||
*/
|
||||
const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
||||
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||
if (collectionAuth) {
|
||||
if (collectionAuth && ['inherit', 'none'].includes(request.auth.mode)) {
|
||||
switch (collectionAuth.mode) {
|
||||
case 'awsv4':
|
||||
axiosRequest.awsv4config = {
|
||||
@@ -62,6 +69,36 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
||||
password: get(collectionAuth, 'digest.password')
|
||||
};
|
||||
break;
|
||||
case 'oauth2':
|
||||
const grantType = get(collectionAuth, 'auth.oauth2.grantType');
|
||||
switch (grantType) {
|
||||
case 'password':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
username: get(collectionAuth, 'auth.oauth2.username'),
|
||||
password: get(collectionAuth, 'auth.oauth2.password')
|
||||
};
|
||||
break;
|
||||
case 'authorization_code':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
callbackUrl: get(collectionAuth, 'auth.oauth2.callbackUrl'),
|
||||
authorizationUrl: get(collectionAuth, 'auth.oauth2.authorizationUrl'),
|
||||
accessTokenUrl: get(collectionAuth, 'auth.oauth2.accessTokenUrl'),
|
||||
clientId: get(collectionAuth, 'auth.oauth2.clientId'),
|
||||
clientSecret: get(collectionAuth, 'auth.oauth2.clientSecret'),
|
||||
scope: get(collectionAuth, 'auth.oauth2.scope')
|
||||
};
|
||||
break;
|
||||
case 'client_credentials':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
clientId: get(collectionAuth, 'auth.oauth2.clientId'),
|
||||
clientSecret: get(collectionAuth, 'auth.oauth2.clientSecret')
|
||||
};
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +128,37 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
||||
username: get(request, 'auth.digest.username'),
|
||||
password: get(request, 'auth.digest.password')
|
||||
};
|
||||
break;
|
||||
case 'oauth2':
|
||||
const grantType = get(request, 'auth.oauth2.grantType');
|
||||
switch (grantType) {
|
||||
case 'password':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
username: get(request, 'auth.oauth2.username'),
|
||||
password: get(request, 'auth.oauth2.password')
|
||||
};
|
||||
break;
|
||||
case 'authorization_code':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
callbackUrl: get(request, 'auth.oauth2.callbackUrl'),
|
||||
authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'),
|
||||
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
|
||||
clientId: get(request, 'auth.oauth2.clientId'),
|
||||
clientSecret: get(request, 'auth.oauth2.clientSecret'),
|
||||
scope: get(request, 'auth.oauth2.scope')
|
||||
};
|
||||
break;
|
||||
case 'client_credentials':
|
||||
axiosRequest.oauth2 = {
|
||||
grantType: grantType,
|
||||
clientId: get(request, 'auth.oauth2.clientId'),
|
||||
clientSecret: get(request, 'auth.oauth2.clientSecret')
|
||||
};
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class EnvironmentSecretsStore {
|
||||
}
|
||||
|
||||
isValidValue(val) {
|
||||
return val && typeof val === 'string' && val.length > 0;
|
||||
return typeof val === 'string' && val.length >= 0;
|
||||
}
|
||||
|
||||
storeEnvSecrets(collectionPathname, environment) {
|
||||
|
||||
@@ -48,7 +48,7 @@ function safeStorageDecrypt(str) {
|
||||
}
|
||||
|
||||
function encryptString(str) {
|
||||
if (!str || typeof str !== 'string' || str.length === 0) {
|
||||
if (typeof str !== 'string') {
|
||||
throw new Error('Encrypt failed: invalid string');
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ describe('Encryption and Decryption Tests', () => {
|
||||
|
||||
it('encrypt should throw an error for invalid string', () => {
|
||||
expect(() => encryptString(null)).toThrow('Encrypt failed: invalid string');
|
||||
expect(() => encryptString('')).toThrow('Encrypt failed: invalid string');
|
||||
});
|
||||
|
||||
it('decrypt should throw an error for invalid string', () => {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"dependencies": {
|
||||
"@usebruno/query": "0.1.0",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"atob": "^2.1.2",
|
||||
"axios": "^1.5.1",
|
||||
"btoa": "^1.2.1",
|
||||
@@ -28,7 +29,7 @@
|
||||
"moment": "^2.29.4",
|
||||
"nanoid": "3.3.4",
|
||||
"node-fetch": "2.*",
|
||||
"uuid": "^9.0.0",
|
||||
"node-vault": "^0.10.2"
|
||||
"node-vault": "^0.10.2",
|
||||
"uuid": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const { cleanJson } = require('../utils');
|
||||
|
||||
// Inbuilt Library Support
|
||||
const ajv = require('ajv');
|
||||
const addFormats = require('ajv-formats');
|
||||
const atob = require('atob');
|
||||
const btoa = require('btoa');
|
||||
const lodash = require('lodash');
|
||||
@@ -102,6 +103,7 @@ class ScriptRuntime {
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
atob,
|
||||
btoa,
|
||||
lodash,
|
||||
@@ -194,6 +196,7 @@ class ScriptRuntime {
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
atob,
|
||||
btoa,
|
||||
lodash,
|
||||
|
||||
@@ -19,6 +19,7 @@ const { cleanJson } = require('../utils');
|
||||
|
||||
// Inbuilt Library Support
|
||||
const ajv = require('ajv');
|
||||
const addFormats = require('ajv-formats');
|
||||
const atob = require('atob');
|
||||
const btoa = require('btoa');
|
||||
const lodash = require('lodash');
|
||||
@@ -120,6 +121,7 @@ class TestRuntime {
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
btoa,
|
||||
atob,
|
||||
lodash,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { describe, it, expect } = require('@jest/globals');
|
||||
const TestRuntime = require('../src/runtime/test-runtime');
|
||||
const ScriptRuntime = require('../src/runtime/script-runtime');
|
||||
|
||||
describe('runtime', () => {
|
||||
describe('test-runtime', () => {
|
||||
@@ -49,5 +50,129 @@ describe('runtime', () => {
|
||||
{ description: 'async test', status: 'pass' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have ajv and ajv-formats dependencies available', async () => {
|
||||
const testFile = `
|
||||
const Ajv = require('ajv');
|
||||
const addFormats = require("ajv-formats");
|
||||
const ajv = new Ajv();
|
||||
addFormats(ajv);
|
||||
|
||||
const schema = {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
};
|
||||
|
||||
const validate = ajv.compile(schema)
|
||||
|
||||
test('format valid', () => {
|
||||
const valid = validate(new Date().toISOString())
|
||||
expect(valid).to.be.true;
|
||||
})
|
||||
`;
|
||||
|
||||
const runtime = new TestRuntime();
|
||||
const result = await runtime.runTests(
|
||||
testFile,
|
||||
{ ...baseRequest },
|
||||
{ ...baseResponse },
|
||||
{},
|
||||
{},
|
||||
'.',
|
||||
null,
|
||||
process.env
|
||||
);
|
||||
expect(result.results.map((el) => ({ description: el.description, status: el.status }))).toEqual([
|
||||
{ description: 'format valid', status: 'pass' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('script-runtime', () => {
|
||||
describe('run-request-script', () => {
|
||||
const baseRequest = {
|
||||
method: 'GET',
|
||||
url: 'http://localhost:3000/',
|
||||
headers: {},
|
||||
data: undefined
|
||||
};
|
||||
|
||||
it('should have ajv and ajv-formats dependencies available', async () => {
|
||||
const script = `
|
||||
const Ajv = require('ajv');
|
||||
const addFormats = require("ajv-formats");
|
||||
const ajv = new Ajv();
|
||||
addFormats(ajv);
|
||||
|
||||
const schema = {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
};
|
||||
|
||||
const validate = ajv.compile(schema)
|
||||
|
||||
bru.setVar('validation', validate(new Date().toISOString()))
|
||||
`;
|
||||
|
||||
const runtime = new ScriptRuntime();
|
||||
const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env);
|
||||
expect(result.collectionVariables.validation).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('run-response-script', () => {
|
||||
const baseRequest = {
|
||||
method: 'GET',
|
||||
url: 'http://localhost:3000/',
|
||||
headers: {},
|
||||
data: undefined
|
||||
};
|
||||
const baseResponse = {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
data: [
|
||||
{
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
id: 2
|
||||
},
|
||||
{
|
||||
id: 3
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
it('should have ajv and ajv-formats dependencies available', async () => {
|
||||
const script = `
|
||||
const Ajv = require('ajv');
|
||||
const addFormats = require("ajv-formats");
|
||||
const ajv = new Ajv();
|
||||
addFormats(ajv);
|
||||
|
||||
const schema = {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
};
|
||||
|
||||
const validate = ajv.compile(schema)
|
||||
|
||||
bru.setVar('validation', validate(new Date().toISOString()))
|
||||
`;
|
||||
|
||||
const runtime = new ScriptRuntime();
|
||||
const result = await runtime.runResponseScript(
|
||||
script,
|
||||
{ ...baseRequest },
|
||||
{ ...baseResponse },
|
||||
{},
|
||||
{},
|
||||
'.',
|
||||
null,
|
||||
process.env
|
||||
);
|
||||
expect(result.collectionVariables.validation).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ const safeParseJson = (json) => {
|
||||
|
||||
const indentString = (str) => {
|
||||
if (!str || !str.length) {
|
||||
return str;
|
||||
return str || '';
|
||||
}
|
||||
|
||||
return str
|
||||
@@ -20,7 +20,7 @@ const indentString = (str) => {
|
||||
|
||||
const outdentString = (str) => {
|
||||
if (!str || !str.length) {
|
||||
return str;
|
||||
return str || '';
|
||||
}
|
||||
|
||||
return str
|
||||
|
||||
@@ -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 = authawsv4 | authbasic | authbearer | authdigest
|
||||
auths = authawsv4 | authbasic | authbearer | authdigest | authOAuth2
|
||||
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body
|
||||
bodyforms = bodyformurlencoded | bodymultipart
|
||||
|
||||
@@ -80,6 +80,7 @@ const grammar = ohm.grammar(`Bru {
|
||||
authbasic = "auth:basic" dictionary
|
||||
authbearer = "auth:bearer" dictionary
|
||||
authdigest = "auth:digest" dictionary
|
||||
authOAuth2 = "auth:oauth2" dictionary
|
||||
|
||||
body = "body" st* "{" nl* textblock tagend
|
||||
bodyjson = "body:json" st* "{" nl* textblock tagend
|
||||
@@ -380,6 +381,46 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
||||
}
|
||||
};
|
||||
},
|
||||
authOAuth2(_1, dictionary) {
|
||||
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||
const grantTypeKey = _.find(auth, { name: 'grant_type' });
|
||||
const usernameKey = _.find(auth, { name: 'username' });
|
||||
const passwordKey = _.find(auth, { name: 'password' });
|
||||
const callbackUrlKey = _.find(auth, { name: 'callback_url' });
|
||||
const authorizationUrlKey = _.find(auth, { name: 'authorization_url' });
|
||||
const accessTokenUrlKey = _.find(auth, { name: 'access_token_url' });
|
||||
const clientIdKey = _.find(auth, { name: 'client_id' });
|
||||
const clientSecretKey = _.find(auth, { name: 'client_secret' });
|
||||
const scopeKey = _.find(auth, { name: 'scope' });
|
||||
return {
|
||||
auth: {
|
||||
oauth2:
|
||||
grantTypeKey?.value && grantTypeKey?.value == 'password'
|
||||
? {
|
||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||
username: usernameKey ? usernameKey.value : '',
|
||||
password: passwordKey ? passwordKey.value : ''
|
||||
}
|
||||
: grantTypeKey?.value && grantTypeKey?.value == 'authorization_code'
|
||||
? {
|
||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
|
||||
authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '',
|
||||
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
|
||||
clientId: clientIdKey ? clientIdKey.value : '',
|
||||
clientSecret: clientSecretKey ? clientSecretKey.value : '',
|
||||
scope: scopeKey ? scopeKey.value : ''
|
||||
}
|
||||
: grantTypeKey?.value && grantTypeKey?.value == 'client_credentials'
|
||||
? {
|
||||
grantType: grantTypeKey ? grantTypeKey.value : '',
|
||||
clientId: clientIdKey ? clientIdKey.value : '',
|
||||
clientSecret: clientSecretKey ? clientSecretKey.value : ''
|
||||
}
|
||||
: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
bodyformurlencoded(_1, dictionary) {
|
||||
return {
|
||||
body: {
|
||||
|
||||
@@ -89,12 +89,12 @@ const jsonToBru = (json) => {
|
||||
|
||||
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}`)}
|
||||
${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 || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
@@ -102,8 +102,8 @@ ${indentString(`profileName: ${auth.awsv4.profileName}`)}
|
||||
|
||||
if (auth && auth.basic) {
|
||||
bru += `auth:basic {
|
||||
${indentString(`username: ${auth.basic.username}`)}
|
||||
${indentString(`password: ${auth.basic.password}`)}
|
||||
${indentString(`username: ${auth?.basic?.username || ''}`)}
|
||||
${indentString(`password: ${auth?.basic?.password || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
@@ -111,7 +111,7 @@ ${indentString(`password: ${auth.basic.password}`)}
|
||||
|
||||
if (auth && auth.bearer) {
|
||||
bru += `auth:bearer {
|
||||
${indentString(`token: ${auth.bearer.token}`)}
|
||||
${indentString(`token: ${auth?.bearer?.token || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
@@ -119,13 +119,49 @@ ${indentString(`token: ${auth.bearer.token}`)}
|
||||
|
||||
if (auth && auth.digest) {
|
||||
bru += `auth:digest {
|
||||
${indentString(`username: ${auth.digest.username}`)}
|
||||
${indentString(`password: ${auth.digest.password}`)}
|
||||
${indentString(`username: ${auth?.digest?.username || ''}`)}
|
||||
${indentString(`password: ${auth?.digest?.password || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
if (auth && auth.oauth2) {
|
||||
switch (auth?.oauth2?.grantType) {
|
||||
case 'password':
|
||||
bru += `auth:oauth2 {
|
||||
${indentString(`grant_type: password`)}
|
||||
${indentString(`username: ${auth?.oauth2?.username || ''}`)}
|
||||
${indentString(`password: ${auth?.oauth2?.password || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
break;
|
||||
case 'authorization_code':
|
||||
bru += `auth:oauth2 {
|
||||
${indentString(`grant_type: authorization_code`)}
|
||||
${indentString(`callback_url: ${auth?.oauth2?.callbackUrl || ''}`)}
|
||||
${indentString(`authorization_url: ${auth?.oauth2?.authorizationUrl || ''}`)}
|
||||
${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
|
||||
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
||||
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
break;
|
||||
case 'client_credentials':
|
||||
bru += `auth:oauth2 {
|
||||
${indentString(`grant_type: client_credentials`)}
|
||||
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
|
||||
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
|
||||
}
|
||||
|
||||
`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (body && body.json && body.json.length) {
|
||||
bru += `body:json {
|
||||
${indentString(body.json)}
|
||||
@@ -332,3 +368,5 @@ ${indentString(docs)}
|
||||
};
|
||||
|
||||
module.exports = jsonToBru;
|
||||
|
||||
// alternative to writing the below code to avoif undefined
|
||||
|
||||
@@ -45,6 +45,16 @@ auth:digest {
|
||||
password: secret
|
||||
}
|
||||
|
||||
auth:oauth2 {
|
||||
grant_type: authorization_code
|
||||
callback_url: http://localhost:8080/api/auth/oauth2/ac/callback
|
||||
authorization_url: http://localhost:8080/api/auth/oauth2/ac/authorize
|
||||
access_token_url: http://localhost:8080/api/auth/oauth2/ac/token
|
||||
client_id: client_id_1
|
||||
client_secret: client_secret_1
|
||||
scope: read write
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"hello": "world"
|
||||
|
||||
@@ -63,6 +63,15 @@
|
||||
"digest": {
|
||||
"username": "john",
|
||||
"password": "secret"
|
||||
},
|
||||
"oauth2": {
|
||||
"grantType": "authorization_code",
|
||||
"clientId": "client_id_1",
|
||||
"clientSecret": "client_secret_1",
|
||||
"authorizationUrl": "http://localhost:8080/api/auth/oauth2/ac/authorize",
|
||||
"callbackUrl": "http://localhost:8080/api/auth/oauth2/ac/callback",
|
||||
"accessTokenUrl": "http://localhost:8080/api/auth/oauth2/ac/token",
|
||||
"scope": "read write"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
|
||||
@@ -119,12 +119,63 @@ const authDigestSchema = Yup.object({
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
const oauth2Schema = Yup.object({
|
||||
grantType: Yup.string()
|
||||
.oneOf(['client_credentials', 'password', 'authorization_code'])
|
||||
.required('grantType is required'),
|
||||
username: Yup.string().when('grantType', {
|
||||
is: (val) => ['client_credentials', 'password'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
password: Yup.string().when('grantType', {
|
||||
is: (val) => ['client_credentials', 'password'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
callbackUrl: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
authorizationUrl: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
accessTokenUrl: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
clientId: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code', 'client_credentials'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
clientSecret: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code', 'client_credentials'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
}),
|
||||
scope: Yup.string().when('grantType', {
|
||||
is: (val) => ['authorization_code'].includes(val),
|
||||
then: Yup.string().nullable(),
|
||||
otherwise: Yup.string().nullable().strip()
|
||||
})
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
const authSchema = Yup.object({
|
||||
mode: Yup.string().oneOf(['none', 'awsv4', 'basic', 'bearer', 'digest']).required('mode is required'),
|
||||
mode: Yup.string()
|
||||
.oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'oauth2'])
|
||||
.required('mode is required'),
|
||||
awsv4: authAwsV4Schema.nullable(),
|
||||
basic: authBasicSchema.nullable(),
|
||||
bearer: authBearerSchema.nullable(),
|
||||
digest: authDigestSchema.nullable()
|
||||
digest: authDigestSchema.nullable(),
|
||||
oauth2: oauth2Schema.nullable()
|
||||
})
|
||||
.noUnknown(true)
|
||||
.strict();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
meta {
|
||||
name: Bearer Auth 401
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{host}}/api/auth/bearer/protected
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{bearerAuthToken}}zz
|
||||
}
|
||||
|
||||
assert {
|
||||
res.status: 401
|
||||
res.body.message: Unauthorized
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
bru.setEnvVar("foo", "bar");
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
meta {
|
||||
name: Bearer Auth 401
|
||||
name: inherit Bearer Auth 200
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{host}}/api/auth/bearer/protected
|
||||
body: json
|
||||
auth: none
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
assert {
|
||||
res.status: 401
|
||||
res.body.message: Unauthorized
|
||||
res.status: 200
|
||||
res.body.message: Authentication successful
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
@@ -15,7 +15,7 @@
|
||||
"bypassProxy": ""
|
||||
},
|
||||
"scripts": {
|
||||
"moduleWhitelist": ["crypto"],
|
||||
"moduleWhitelist": ["crypto", "buffer"],
|
||||
"filesystemAccess": {
|
||||
"allow": true
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ headers {
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: none
|
||||
mode: bearer
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
@@ -12,7 +12,7 @@ auth:basic {
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{bearerAuthToken}}
|
||||
token: {{bearer_auth_token}}
|
||||
}
|
||||
|
||||
docs {
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
vars {
|
||||
host: http://localhost:80
|
||||
host: http://localhost:8080
|
||||
bearer_auth_token: your_secret_token
|
||||
basic_auth_password: della
|
||||
env.var1: envVar1
|
||||
env-var2: envVar2
|
||||
bark: {{process.env.PROC_ENV_VAR}}
|
||||
client_id: client_id_1
|
||||
client_secret: client_secret_1
|
||||
auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize
|
||||
callback_url: http://localhost:8080/api/auth/oauth2/ac/callback
|
||||
access_token_url: http://localhost:8080/api/auth/oauth2/ac/token
|
||||
ropc_username: foo
|
||||
ropc_password: bar
|
||||
github_authorize_url: https://github.com/login/oauth/authorize
|
||||
github_access_token_url: https://github.com/login/oauth/access_token
|
||||
google_auth_url: https://accounts.google.com/o/oauth2/auth
|
||||
google_access_token_url: https://accounts.google.com/o/oauth2/token
|
||||
google_scope: https://www.googleapis.com/auth/userinfo.email
|
||||
}
|
||||
vars:secret [
|
||||
github_client_secret,
|
||||
github_client_id,
|
||||
google_client_id,
|
||||
google_client_secret,
|
||||
github_authorization_code,
|
||||
ropc_access_token,
|
||||
cc_access_token,
|
||||
ac_access_token,
|
||||
github_access_token
|
||||
]
|
||||
|
||||
24
packages/bruno-tests/collection/graphql/spacex.bru
Normal file
24
packages/bruno-tests/collection/graphql/spacex.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: spacex
|
||||
type: graphql
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://spacex-production.up.railway.app/
|
||||
body: graphql
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:graphql {
|
||||
{
|
||||
company {
|
||||
ceo
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
assert {
|
||||
res.status: eq 200
|
||||
}
|
||||
151
packages/bruno-tests/collection/package-lock.json
generated
151
packages/bruno-tests/collection/package-lock.json
generated
@@ -8,7 +8,9 @@
|
||||
"name": "@usebruno/test-collection",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^8.4.0"
|
||||
"@faker-js/faker": "^8.4.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lru-map-cache": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@faker-js/faker": {
|
||||
@@ -25,6 +27,153 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||
"npm": ">=6.14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-map-cache": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-map-cache/-/lru-map-cache-0.1.0.tgz",
|
||||
"integrity": "sha512-r1lasvJbg3lrTS37W5h4Ugy9miaWluYqviZGbfH9A6AbjxSDJCtPNqtGr5MRl/RG/EfYrwe07DC4zQEBnY2q4w=="
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
"name": "@usebruno/test-collection",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^8.4.0"
|
||||
"@faker-js/faker": "^8.4.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lru-map-cache": "^0.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/bruno-tests/collection_oauth2/.env
Normal file
1
packages/bruno-tests/collection_oauth2/.env
Normal file
@@ -0,0 +1 @@
|
||||
PROC_ENV_VAR=woof
|
||||
1
packages/bruno-tests/collection_oauth2/.gitignore
vendored
Normal file
1
packages/bruno-tests/collection_oauth2/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.env
|
||||
1
packages/bruno-tests/collection_oauth2/.nvmrc
Normal file
1
packages/bruno-tests/collection_oauth2/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
v18
|
||||
@@ -0,0 +1,25 @@
|
||||
meta {
|
||||
name: github token with authorize
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: github.com
|
||||
body: none
|
||||
auth: oauth2
|
||||
}
|
||||
|
||||
auth:oauth2 {
|
||||
grant_type: authorization_code
|
||||
callback_url: {{callback_url}}
|
||||
authorization_url: {{github_authorize_url}}
|
||||
access_token_url: {{github_access_token_url}}
|
||||
client_id: {{github_client_id}}
|
||||
client_secret: {{github_client_secret}}
|
||||
scope: repo,gist
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
bru.setEnvVar('github_access_token',res.body.split('access_token=')[1]?.split('&scope')[0]);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
meta {
|
||||
name: google token with authorize
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url:
|
||||
body: none
|
||||
auth: oauth2
|
||||
}
|
||||
|
||||
auth:oauth2 {
|
||||
grant_type: authorization_code
|
||||
callback_url: {{callback_url}}
|
||||
authorization_url: {{google_auth_url}}
|
||||
access_token_url: {{google_access_token_url}}
|
||||
client_id: {{google_client_id}}
|
||||
client_secret: {{google_client_secret}}
|
||||
scope: {{google_scope}}
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
bru.setEnvVar('ac_access_token', res.body.access_token);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
meta {
|
||||
name: resource
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/auth/oauth2/ac/resource?token={{ac_access_token}}
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
query {
|
||||
token: {{ac_access_token}}
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token:
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"code": "eb30dbf783b65bec4539ee1dcb068606",
|
||||
"client_id": "{{client_id}}",
|
||||
"client_secret": "{{client_secret}}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
meta {
|
||||
name: token with authorize
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url:
|
||||
body: none
|
||||
auth: oauth2
|
||||
}
|
||||
|
||||
auth:oauth2 {
|
||||
grant_type: authorization_code
|
||||
callback_url: {{callback_url}}
|
||||
authorization_url: {{auth_url}}
|
||||
access_token_url: {{access_token_url}}
|
||||
client_id: {{client_id}}
|
||||
client_secret: {{client_secret}}
|
||||
scope:
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
bru.setEnvVar('ac_access_token', res.body.access_token);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: resource
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{host}}/api/auth/oauth2/cc/resource?token={{cc_access_token}}
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
query {
|
||||
token: {{cc_access_token}}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: token
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/auth/oauth2/cc/token
|
||||
body: none
|
||||
auth: oauth2
|
||||
}
|
||||
|
||||
auth:oauth2 {
|
||||
grant_type: client_credentials
|
||||
client_id: {{client_id}}
|
||||
client_secret: {{client_secret}}
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
bru.setEnvVar('cc_access_token', res.body.access_token);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: resource
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/auth/oauth2/ropc/resource
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ropc_access_token}}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: token
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/auth/oauth2/ropc/token
|
||||
body: none
|
||||
auth: oauth2
|
||||
}
|
||||
|
||||
auth:oauth2 {
|
||||
grant_type: password
|
||||
username: {{ropc_username}}
|
||||
password: {{ropc_password}}
|
||||
}
|
||||
|
||||
script:post-response {
|
||||
bru.setEnvVar('ropc_access_token', res.body.access_token);
|
||||
}
|
||||
31
packages/bruno-tests/collection_oauth2/bruno.json
Normal file
31
packages/bruno-tests/collection_oauth2/bruno.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "bruno-testbench",
|
||||
"type": "collection",
|
||||
"proxy": {
|
||||
"enabled": false,
|
||||
"protocol": "http",
|
||||
"hostname": "{{proxyHostname}}",
|
||||
"port": 4000,
|
||||
"auth": {
|
||||
"enabled": false,
|
||||
"username": "anoop",
|
||||
"password": "password"
|
||||
},
|
||||
"bypassProxy": ""
|
||||
},
|
||||
"scripts": {
|
||||
"moduleWhitelist": ["crypto"],
|
||||
"filesystemAccess": {
|
||||
"allow": true
|
||||
}
|
||||
},
|
||||
"clientCertificates": {
|
||||
"enabled": true,
|
||||
"certs": []
|
||||
},
|
||||
"presets": {
|
||||
"requestType": "http",
|
||||
"requestUrl": "http://localhost:6000"
|
||||
}
|
||||
}
|
||||
22
packages/bruno-tests/collection_oauth2/collection.bru
Normal file
22
packages/bruno-tests/collection_oauth2/collection.bru
Normal file
@@ -0,0 +1,22 @@
|
||||
headers {
|
||||
check: again
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: none
|
||||
}
|
||||
|
||||
auth:basic {
|
||||
username: bruno
|
||||
password: {{basicAuthPassword}}
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{bearerAuthToken}}
|
||||
}
|
||||
|
||||
docs {
|
||||
# bruno-testbench 🐶
|
||||
|
||||
This is a test collection that I am using to test various functionalities around bruno
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
vars {
|
||||
host: http://localhost:8080
|
||||
bearer_auth_token: your_secret_token
|
||||
basic_auth_password: della
|
||||
client_id: client_id_1
|
||||
client_secret: client_secret_1
|
||||
auth_url: http://localhost:8080/api/auth/oauth2/ac/authorize
|
||||
callback_url: http://localhost:8080/api/auth/oauth2/ac/callback
|
||||
access_token_url: http://localhost:8080/api/auth/oauth2/ac/token
|
||||
ropc_username: foo
|
||||
ropc_password: bar
|
||||
github_authorize_url: https://github.com/login/oauth/authorize
|
||||
github_access_token_url: https://github.com/login/oauth/access_token
|
||||
google_auth_url: https://accounts.google.com/o/oauth2/auth
|
||||
google_access_token_url: https://accounts.google.com/o/oauth2/token
|
||||
google_scope: https://www.googleapis.com/auth/userinfo.email
|
||||
}
|
||||
vars:secret [
|
||||
github_client_secret,
|
||||
github_client_id,
|
||||
google_client_id,
|
||||
google_client_secret,
|
||||
github_authorization_code,
|
||||
github_access_token,
|
||||
ac_access_token
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
vars {
|
||||
host: https://testbench-sanity.usebruno.com
|
||||
bearer_auth_token: your_secret_token
|
||||
basic_auth_password: della
|
||||
env.var1: envVar1
|
||||
env-var2: envVar2
|
||||
bark: {{process.env.PROC_ENV_VAR}}
|
||||
}
|
||||
3
packages/bruno-tests/collection_oauth2/file.json
Normal file
3
packages/bruno-tests/collection_oauth2/file.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "bruno"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user