oauth2 fixes

This commit is contained in:
lohxt1
2025-03-20 19:27:14 +05:30
parent 2e4014863f
commit 5a98da2031
30 changed files with 162 additions and 218 deletions

View File

@@ -9,7 +9,7 @@ import OAuth2PasswordCredentials from 'components/RequestPane/Auth/OAuth2/Passwo
import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCredentials/index';
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
const grantTypeComponentMap = (collection) => {
const GrantTypeComponentMap = ({collection }) => {
const dispatch = useDispatch();
const save = () => {
@@ -41,7 +41,7 @@ const OAuth2 = ({ collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<GrantTypeSelector request={request} updateAuth={updateCollectionAuth} collection={collection} />
{grantTypeComponentMap(collection)}
<GrantTypeComponentMap collection={collection} />
</StyledWrapper>
);
};

View File

@@ -10,7 +10,7 @@ import OAuth2ClientCredentials from 'components/RequestPane/Auth/OAuth2/ClientCr
import GrantTypeSelector from 'components/RequestPane/Auth/OAuth2/GrantTypeSelector/index';
import AuthMode from '../AuthMode';
const grantTypeComponentMap = (collection, folder) => {
const GrantTypeComponentMap = ({ collection, folder }) => {
const dispatch = useDispatch();
const save = () => {
@@ -52,7 +52,7 @@ const Auth = ({ collection, folder }) => {
collection={collection}
folder={folder}
/>
{grantTypeComponentMap(collection, folder)}
<GrantTypeComponentMap collection={collection} folder={folder} />
</>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useRef, forwardRef, useState, useEffect } from 'react';
import React, { useRef, forwardRef, useState, useEffect, useMemo } from 'react';
import get from 'lodash/get';
import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux';
@@ -11,7 +11,9 @@ import { inputsConfig } from './inputsConfig';
import toast from 'react-hot-toast';
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
import { cloneDeep, find } from 'lodash';
import { interpolateStringUsingCollectionAndItem } from 'utils/collections/index';
import { getAllVariables } from 'utils/collections/index';
import brunoCommon from '@usebruno/common';
const { interpolate } = brunoCommon;
const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAuth, collection, folder }) => {
const dispatch = useDispatch();
@@ -37,13 +39,20 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
tokenPlacement,
tokenHeaderPrefix,
tokenQueryKey,
refreshUrl,
refreshTokenUrl,
autoRefreshToken,
autoFetchToken
} = oAuth;
const refreshUrlAvailable = refreshUrl?.trim() !== '';
const isAutoRefreshDisabled = !refreshUrlAvailable;
const { uid: collectionUid } = collection;
const interpolatedAccessTokenUrl = useMemo(() => {
const variables = getAllVariables(collection, item);
return interpolate(accessTokenUrl, variables);
}, [collection, item, accessTokenUrl]);
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
const isAutoRefreshDisabled = !refreshTokenUrlAvailable;
const TokenPlacementIcon = forwardRef((props, ref) => {
@@ -127,7 +136,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
tokenPlacement,
tokenHeaderPrefix,
tokenQueryKey,
refreshUrl,
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
[key]: value,
@@ -164,7 +173,6 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
};
const handleClearCache = (e) => {
const interpolatedAccessTokenUrl = interpolateStringUsingCollectionAndItem({ collection, item, string: accessTokenUrl });
dispatch(clearOauth2Cache({ collectionUid: collection?.uid, url: interpolatedAccessTokenUrl, credentialsId }))
.then(() => {
toast.success('cleared cache successfully');
@@ -174,9 +182,7 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
});
};
const { uid: collectionUid } = collection;
const interpolatedUrl = interpolateStringUsingCollectionAndItem({ collection, item, string: accessTokenUrl });
const credentialsData = find(collection?.oauth2Credentials, creds => creds?.url == interpolatedUrl && creds?.collectionUid == collectionUid && creds?.credentialsId == credentialsId);
const credentialsData = find(collection?.oauth2Credentials, creds => creds?.url == interpolatedAccessTokenUrl && creds?.collectionUid == collectionUid && creds?.credentialsId == credentialsId);
const creds = credentialsData?.credentials || {};
useEffect(() => {
@@ -339,10 +345,10 @@ const OAuth2AuthorizationCode = ({ save, item = {}, request, handleRun, updateAu
<label className="block min-w-[140px]">Refresh Token URL</label>
<div className="single-line-editor-wrapper flex-1">
<SingleLineEditor
value={get(request, 'auth.oauth2.refreshUrl', '')}
value={get(request, 'auth.oauth2.refreshTokenUrl', '')}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleChange("refreshUrl", val)}
onChange={(val) => handleChange("refreshTokenUrl", val)}
collection={collection}
item={item}
/>

View File

@@ -11,7 +11,9 @@ import Dropdown from 'components/Dropdown';
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
import toast from 'react-hot-toast';
import { cloneDeep } from 'lodash';
import { interpolateStringUsingCollectionAndItem } from 'utils/collections/index';
import { getAllVariables } from 'utils/collections/index';
import brunoCommon from '@usebruno/common';
const { interpolate } = brunoCommon;
const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => {
const dispatch = useDispatch();
@@ -33,13 +35,18 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
tokenPlacement,
tokenHeaderPrefix,
tokenQueryKey,
refreshUrl,
refreshTokenUrl,
autoRefreshToken,
autoFetchToken
} = oAuth;
const refreshUrlAvailable = refreshUrl?.trim() !== '';
const isAutoRefreshDisabled = !refreshUrlAvailable;
const interpolatedAccessTokenUrl = useMemo(() => {
const variables = getAllVariables(collection, item);
return interpolate(accessTokenUrl, variables);
}, [collection, item, accessTokenUrl]);
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
const isAutoRefreshDisabled = !refreshTokenUrlAvailable;
const handleFetchOauth2Credentials = async () => {
let requestCopy = cloneDeep(request);
@@ -113,7 +120,7 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
tokenPlacement,
tokenHeaderPrefix,
tokenQueryKey,
refreshUrl,
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
[key]: value
@@ -123,7 +130,6 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
};
const handleClearCache = (e) => {
const interpolatedAccessTokenUrl = interpolateStringUsingCollectionAndItem({ collection, item, string: accessTokenUrl });
dispatch(clearOauth2Cache({ collectionUid: collection?.uid, url: interpolatedAccessTokenUrl, credentialsId }))
.then(() => {
toast.success('cleared cache successfully');
@@ -279,10 +285,10 @@ const OAuth2ClientCredentials = ({ save, item = {}, request, handleRun, updateAu
<label className="block min-w-[140px]">Refresh Token URL</label>
<div className="single-line-editor-wrapper flex-1">
<SingleLineEditor
value={get(request, 'auth.oauth2.refreshUrl', '')}
value={get(request, 'auth.oauth2.refreshTokenUrl', '')}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleChange("refreshUrl", val)}
onChange={(val) => handleChange("refreshTokenUrl", val)}
collection={collection}
item={item}
/>

View File

@@ -1,17 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
label {
display: block;
font-size: 0.8125rem;
}
textarea {
height: fit-content;
max-width: 400px;
border: solid 1px ${(props) => props.theme.input.border};
background-color: ${(props) => props.theme.input.bg};
}
`;
export default Wrapper;

View File

@@ -1,80 +0,0 @@
import React, { useEffect, useState } from 'react';
import { readOauth2CachedCredentials } from 'utils/network';
import { sendCollectionOauth2Request, sendRequest, clearOauth2Cache } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
const CredentialsPreview = ({ item, collection }) => {
const oauth2CredentialsAreaRef = React.createRef();
const [oauth2Credentials, setOauth2Credentials] = useState({});
const dispatch = useDispatch();
useEffect(() => {
oauth2CredentialsAreaRef.current.value = oauth2Credentials;
readOauth2CachedCredentials(collection.uid).then((credentials) => setOauth2Credentials(credentials));
}, [oauth2CredentialsAreaRef]);
const handleRun = async () => {
if (item) {
dispatch(sendRequest(item, collection.uid));
} else {
dispatch(sendCollectionOauth2Request(collection.uid));
}
};
const handleClearCache = (e) => {
dispatch(clearOauth2Cache({ collectionUid: collection?.uid, url: '' }))
.then(() => {
readOauth2CachedCredentials(collection.uid).then((credentials) => {
setOauth2Credentials(credentials);
toast.success('Cleared cache successfully');
});
})
.catch((err) => {
toast.error(err.message);
});
};
const sortedFields = () => {
const tokens = {};
const extras = {};
Object.entries(oauth2Credentials).forEach(([key, value]) => {
if (key.endsWith('_token')) {
tokens[key] = value;
} else {
extras[key] = value;
}
});
return { ...tokens, ...extras };
};
return (
<StyledWrapper className="flex flex-col w-full gap-1 mt-4">
<div className="credential-item-wrapper" ref={oauth2CredentialsAreaRef}>
{Object.entries(oauth2Credentials).length > 0 ? (
<>
<button onClick={handleClearCache} className="submit btn btn-sm btn-secondary w-fit">
Clear Access Token Cache
</button>
<details className="cursor-pointer flex flex-row w-full mt-2 gap-2">
<summary>Cached OAuth2 Credentials</summary>
{Object.entries(sortedFields()).map(([field, value]) => (
<div key={field}>
<label className="text-xs">{field}</label>
<textarea className="w-full h-24 p-2 text-xs border rounded" value={value} readOnly />
</div>
))}
</details>
</>
) : (
<button onClick={handleRun} className="submit btn btn-sm btn-secondary w-fit">
Get Access Token
</button>
)}
</div>
</StyledWrapper>
);
};
export default CredentialsPreview;

View File

@@ -1,8 +1,10 @@
import { find } from "lodash";
import { interpolateStringUsingCollectionAndItem } from "utils/collections/index";
import StyledWrapper from "./StyledWrapper";
import { useState, useEffect } from "react";
import { IconChevronDown, IconChevronRight, IconCopy, IconCheck } from '@tabler/icons';
import { getAllVariables } from 'utils/collections/index';
import brunoCommon from '@usebruno/common';
const { interpolate } = brunoCommon;
const TokenSection = ({ title, token }) => {
if (!token) return null;
@@ -126,7 +128,12 @@ const ExpiryTimer = ({ expiresIn }) => {
const Oauth2TokenViewer = ({ collection, item, url, credentialsId, handleRun }) => {
const { uid: collectionUid } = collection;
const interpolatedUrl = interpolateStringUsingCollectionAndItem({ collection, item, string: url });
const interpolatedUrl = useMemo(() => {
const variables = getAllVariables(collection, item);
return interpolate(url, variables);
}, [collection, item, url]);
const credentialsData = find(collection?.oauth2Credentials, creds => creds?.url == interpolatedUrl && creds?.collectionUid == collectionUid && creds?.credentialsId == credentialsId);
const creds = credentialsData?.credentials || {};

View File

@@ -10,8 +10,10 @@ import { inputsConfig } from './inputsConfig';
import Dropdown from 'components/Dropdown';
import Oauth2TokenViewer from '../Oauth2TokenViewer/index';
import toast from 'react-hot-toast';
import { interpolateStringUsingCollectionAndItem } from 'utils/collections/index';
import { cloneDeep } from 'lodash';
import { getAllVariables } from 'utils/collections/index';
import brunoCommon from '@usebruno/common';
const { interpolate } = brunoCommon;
const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, updateAuth, collection }) => {
const dispatch = useDispatch();
@@ -35,13 +37,18 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
tokenPlacement,
tokenHeaderPrefix,
tokenQueryKey,
refreshUrl,
refreshTokenUrl,
autoRefreshToken,
autoFetchToken
} = oAuth;
const refreshUrlAvailable = refreshUrl?.trim() !== '';
const isAutoRefreshDisabled = !refreshUrlAvailable;
const refreshTokenUrlAvailable = refreshTokenUrl?.trim() !== '';
const isAutoRefreshDisabled = !refreshTokenUrlAvailable;
const interpolatedAccessTokenUrl = useMemo(() => {
const variables = getAllVariables(collection, item);
return interpolate(accessTokenUrl, variables);
}, [collection, item, accessTokenUrl]);
const handleFetchOauth2Credentials = async () => {
let requestCopy = cloneDeep(request);
@@ -116,7 +123,7 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
tokenPlacement,
tokenHeaderPrefix,
tokenQueryKey,
refreshUrl,
refreshTokenUrl,
autoRefreshToken,
autoFetchToken,
[key]: value
@@ -126,7 +133,6 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
};
const handleClearCache = (e) => {
const interpolatedAccessTokenUrl = interpolateStringUsingCollectionAndItem({ collection, item, string: accessTokenUrl });
dispatch(clearOauth2Cache({ collectionUid: collection?.uid, url: interpolatedAccessTokenUrl, credentialsId }))
.then(() => {
toast.success('cleared cache successfully');
@@ -282,10 +288,10 @@ const OAuth2PasswordCredentials = ({ save, item = {}, request, handleRun, update
<label className="block min-w-[140px]">Refresh Token URL</label>
<div className="single-line-editor-wrapper flex-1">
<SingleLineEditor
value={get(request, 'auth.oauth2.refreshUrl', '')}
value={get(request, 'auth.oauth2.refreshTokenUrl', '')}
theme={storedTheme}
onSave={handleSave}
onChange={(val) => handleChange("refreshUrl", val)}
onChange={(val) => handleChange("refreshTokenUrl", val)}
collection={collection}
item={item}
/>

View File

@@ -9,7 +9,7 @@ import { updateAuth } from 'providers/ReduxStore/slices/collections';
import { saveRequest, sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
const grantTypeComponentMap = (item, collection) => {
const GrantTypeComponentMap = ({ item, collection }) => {
const dispatch = useDispatch();
const save = () => {
@@ -46,7 +46,7 @@ const OAuth2 = ({ item, collection }) => {
return (
<StyledWrapper className="mt-2 w-full">
<GrantTypeSelector item={item} request={request} updateAuth={updateAuth} collection={collection} />
{grantTypeComponentMap(item, collection)}
<GrantTypeComponentMap item={item} collection={collection} />
</StyledWrapper>
);
};

View File

@@ -68,7 +68,7 @@ const formatErrorMessage = (error) => {
const remoteMethodError = "Error invoking remote method 'send-http-request':";
if (error.includes(remoteMethodError)) {
if (error?.includes(remoteMethodError)) {
const parts = error.split(remoteMethodError);
return parts[1]?.trim() || error;
}
@@ -91,7 +91,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
// Always show raw
const allowedPreviewModes = [{ mode: 'raw', name: 'Raw', uid: uuid() }];
if (mode.includes('html') && typeof data === 'string') {
if (mode?.includes('html') && typeof data === 'string') {
allowedPreviewModes.unshift({ mode: 'preview-web', name: 'Web', uid: uuid() });
} else if (mode.includes('image')) {
allowedPreviewModes.unshift({ mode: 'preview-image', name: 'Image', uid: uuid() });

View File

@@ -298,8 +298,6 @@ export const collectionsSlice = createSlice({
? item.requestSent.timestamp.getTime()
: item?.requestSent?.timestamp || Date.now();
console.log("response reieved", JSON.stringify(item), JSON.stringify(item.requestSent));
// Append the new timeline entry with numeric timestamp
collection.timeline.push({
type: "request",

View File

@@ -383,7 +383,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
di.request.auth.oauth2 = {
grantType: grantType,
accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''),
refreshUrl: get(si.request, 'auth.oauth2.refreshUrl', ''),
refreshTokenUrl: get(si.request, 'auth.oauth2.refreshTokenUrl', ''),
username: get(si.request, 'auth.oauth2.username', ''),
password: get(si.request, 'auth.oauth2.password', ''),
clientId: get(si.request, 'auth.oauth2.clientId', ''),
@@ -404,7 +404,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
callbackUrl: get(si.request, 'auth.oauth2.callbackUrl', ''),
authorizationUrl: get(si.request, 'auth.oauth2.authorizationUrl', ''),
accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''),
refreshUrl: get(si.request, 'auth.oauth2.refreshUrl', ''),
refreshTokenUrl: get(si.request, 'auth.oauth2.refreshTokenUrl', ''),
clientId: get(si.request, 'auth.oauth2.clientId', ''),
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
scope: get(si.request, 'auth.oauth2.scope', ''),
@@ -422,7 +422,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
di.request.auth.oauth2 = {
grantType: grantType,
accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''),
refreshUrl: get(si.request, 'auth.oauth2.refreshUrl', ''),
refreshTokenUrl: get(si.request, 'auth.oauth2.refreshTokenUrl', ''),
clientId: get(si.request, 'auth.oauth2.clientId', ''),
clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''),
scope: get(si.request, 'auth.oauth2.scope', ''),
@@ -1056,13 +1056,6 @@ const mergeVars = (collection, requestTreePath = []) => {
};
};
export const interpolateStringUsingCollectionAndItem = ({ collection, item, string }) => {
const variables = getAllVariables(collection, item);
const value = interpolate(string, variables);
return value;
}
export const getEnvVars = (environment = {}) => {
const variables = environment.variables;
if (!variables || !variables.length) {

View File

@@ -451,7 +451,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
authorizationUrl: findValueUsingKey('authUrl'),
callbackUrl: findValueUsingKey('redirect_uri'),
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshUrl: findValueUsingKey('refreshTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),
@@ -465,7 +465,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
brunoRequestItem.request.auth.oauth2 = {
grantType: 'password',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshUrl: findValueUsingKey('refreshTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
username: findValueUsingKey('username'),
password: findValueUsingKey('password'),
clientId: findValueUsingKey('clientId'),
@@ -480,7 +480,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
brunoRequestItem.request.auth.oauth2 = {
grantType: 'client_credentials',
accessTokenUrl: findValueUsingKey('accessTokenUrl'),
refreshUrl: findValueUsingKey('refreshTokenUrl'),
refreshTokenUrl: findValueUsingKey('refreshTokenUrl'),
clientId: findValueUsingKey('clientId'),
clientSecret: findValueUsingKey('clientSecret'),
scope: findValueUsingKey('scope'),

View File

@@ -41,13 +41,6 @@ export const sendCollectionOauth2Request = async (collection, environment, runti
});
};
export const readOauth2CachedCredentials = async (uid) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer.invoke('read-oauth2-cached-credentials', uid).then(resolve).catch(reject);
});
};
export const fetchGqlSchema = async (endpoint, environment, request, collection) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;

View File

@@ -24,6 +24,7 @@ const Watcher = require('./app/watcher');
const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window');
const registerNotificationsIpc = require('./ipc/notifications');
const registerGlobalEnvironmentsIpc = require('./ipc/global-environments');
const { safeParseJSON, safeStringifyJSON } = require('./utils/common');
const lastOpenedCollections = new LastOpenedCollections();
@@ -160,6 +161,16 @@ app.on('ready', async () => {
return { action: 'deny' };
});
mainWindow.webContents.on('did-finish-load', () => {
let ogSend = mainWindow.webContents.send;
mainWindow.webContents.send = function(channel, ...args) {
return ogSend.apply(this, [channel, ...args?.map(_ => {
// todo: replace this with @msgpack/msgpack encode/decode
return safeParseJSON(safeStringifyJSON(_));
})]);
}
});
// register all ipc handlers
registerNetworkIpc(mainWindow);
registerGlobalEnvironmentsIpc(mainWindow);

View File

@@ -6,6 +6,7 @@ const electronApp = require("electron");
const { setupProxyAgents } = require('../../utils/proxy-util');
const { addCookieToJar, getCookieStringForUrl } = require('../../utils/cookies');
const { preferencesUtil } = require('../../store/preferences');
const { safeStringifyJSON } = require('../../utils/common');
const LOCAL_IPV6 = '::1';
const LOCAL_IPV4 = '127.0.0.1';
@@ -98,7 +99,6 @@ function makeAxiosInstance({
instance.interceptors.request.use(async (config) => {
const url = URL.parse(config.url);
config.metadata = config.metadata || {};
config.metadata.startTime = new Date().getTime();
config.metadata.timeline = config.metadata.timeline || [];
@@ -229,6 +229,7 @@ function makeAxiosInstance({
return response;
},
(error) => {
console.log("interceptors response error ?>>>>>>>>>>>>>>>>>>>>>>>", Boolean(error?.response), Boolean(error), error);
if (error.response) {
const end = Date.now();
const start = error.config.headers['request-start-time'];
@@ -327,6 +328,21 @@ function makeAxiosInstance({
return instance(requestConfig);
}
}
else if (error?.code) {
let metadata = error?.config?.metadata;
metadata.timeline.push({
timestamp: new Date(),
type: 'error',
message: safeStringifyJSON(error?.errors)
});
return {
status: '-',
statusText: error.code,
headers: error?.config?.headers,
data: 'request failed, check timeline network logs',
timeline: metadata.timeline
};
}
return Promise.reject(error);
}
);

View File

@@ -595,16 +595,21 @@ const registerNetworkIpc = (mainWindow) => {
processEnvVars,
collectionPath
);
const requestData = request.mode == 'file'? "<request body redacted>": (typeof request?.data === 'string' ? request?.data : safeStringifyJSON(request?.data));
let requestSent = {
url: request.url,
method: request.method,
headers: request.headers,
data: requestData,
timestamp: Date.now()
}
if (requestData) {
requestSent.dataBuffer = Buffer.from(requestData);
}
!runInBackground && mainWindow.webContents.send('main:run-request-event', {
type: 'request-sent',
requestSent: {
url: request.url,
method: request.method,
headers: request.headers,
data: request.mode == 'file'? "<request body redacted>": safeParseJSON(safeStringifyJSON(request.data)) ,
timestamp: Date.now()
},
requestSent,
collectionUid,
itemUid: item.uid,
requestUid,
@@ -800,17 +805,6 @@ const registerNetworkIpc = (mainWindow) => {
});
});
ipcMain.handle('read-oauth2-cached-credentials', async (event, uid) => {
return new Promise((resolve, reject) => {
try {
const oauth2Store = new Oauth2Store();
return resolve(oauth2Store.getOauth2DataOfCollection(uid).credentials ?? {});
} catch (err) {
reject(new Error('Could not read cached oauth2 credentials'));
}
});
});
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
return new Promise((resolve, reject) => {
if (cancelTokenUid && cancelTokens[cancelTokenUid]) {
@@ -1036,17 +1030,23 @@ const registerNetworkIpc = (mainWindow) => {
continue;
}
const requestData = request.mode == 'file'? "<request body redacted>": (typeof request?.data === 'string' ? request?.data : safeStringifyJSON(request?.data));
let requestSent = {
url: request.url,
method: request.method,
headers: request.headers,
data: requestData
}
if (requestData) {
requestSent.dataBuffer = Buffer.from(requestData);
}
// todo:
// i have no clue why electron can't send the request object
// without safeParseJSON(safeStringifyJSON(request.data))
mainWindow.webContents.send('main:run-folder-event', {
type: 'request-sent',
requestSent: {
url: request.url,
method: request.method,
headers: request.headers,
data: safeParseJSON(safeStringifyJSON(request.data))
},
requestSent,
...eventData
});

View File

@@ -166,7 +166,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
switch (request.oauth2.grantType) {
case 'password':
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
request.oauth2.refreshUrl = _interpolate(request.oauth2.refreshUrl) || '';
request.oauth2.refreshTokenUrl = _interpolate(request.oauth2.refreshTokenUrl) || '';
request.oauth2.username = _interpolate(request.oauth2.username) || '';
request.oauth2.password = _interpolate(request.oauth2.password) || '';
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
@@ -184,7 +184,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
request.oauth2.callbackUrl = _interpolate(request.oauth2.callbackUrl) || '';
request.oauth2.authorizationUrl = _interpolate(request.oauth2.authorizationUrl) || '';
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
request.oauth2.refreshUrl = _interpolate(request.oauth2.refreshUrl) || '';
request.oauth2.refreshTokenUrl = _interpolate(request.oauth2.refreshTokenUrl) || '';
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';
@@ -200,7 +200,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
break;
case 'client_credentials':
request.oauth2.accessTokenUrl = _interpolate(request.oauth2.accessTokenUrl) || '';
request.oauth2.refreshUrl = _interpolate(request.oauth2.refreshUrl) || '';
request.oauth2.refreshTokenUrl = _interpolate(request.oauth2.refreshTokenUrl) || '';
request.oauth2.clientId = _interpolate(request.oauth2.clientId) || '';
request.oauth2.clientSecret = _interpolate(request.oauth2.clientSecret) || '';
request.oauth2.scope = _interpolate(request.oauth2.scope) || '';

View File

@@ -75,7 +75,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
axiosRequest.oauth2 = {
grantType: grantType,
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'),
username: get(collectionAuth, 'oauth2.username'),
password: get(collectionAuth, 'oauth2.password'),
clientId: get(collectionAuth, 'oauth2.clientId'),
@@ -96,7 +96,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
callbackUrl: get(collectionAuth, 'oauth2.callbackUrl'),
authorizationUrl: get(collectionAuth, 'oauth2.authorizationUrl'),
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'),
clientId: get(collectionAuth, 'oauth2.clientId'),
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
scope: get(collectionAuth, 'oauth2.scope'),
@@ -115,7 +115,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
axiosRequest.oauth2 = {
grantType: grantType,
accessTokenUrl: get(collectionAuth, 'oauth2.accessTokenUrl'),
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'),
clientId: get(collectionAuth, 'oauth2.clientId'),
clientSecret: get(collectionAuth, 'oauth2.clientSecret'),
scope: get(collectionAuth, 'oauth2.scope'),
@@ -173,7 +173,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
axiosRequest.oauth2 = {
grantType: grantType,
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'),
username: get(request, 'auth.oauth2.username'),
password: get(request, 'auth.oauth2.password'),
clientId: get(request, 'auth.oauth2.clientId'),
@@ -194,7 +194,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
callbackUrl: get(request, 'auth.oauth2.callbackUrl'),
authorizationUrl: get(request, 'auth.oauth2.authorizationUrl'),
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'),
clientId: get(request, 'auth.oauth2.clientId'),
clientSecret: get(request, 'auth.oauth2.clientSecret'),
scope: get(request, 'auth.oauth2.scope'),
@@ -213,7 +213,7 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
axiosRequest.oauth2 = {
grantType: grantType,
accessTokenUrl: get(request, 'auth.oauth2.accessTokenUrl'),
refreshUrl: get(collectionAuth, 'oauth2.refreshUrl'),
refreshTokenUrl: get(collectionAuth, 'oauth2.refreshTokenUrl'),
clientId: get(request, 'auth.oauth2.clientId'),
clientSecret: get(request, 'auth.oauth2.clientSecret'),
scope: get(request, 'auth.oauth2.scope'),

View File

@@ -1,6 +1,7 @@
const _ = require('lodash');
const Store = require('electron-store');
const { uuid } = require('../utils/common');
const { uuid, safeStringifyJSON, safeParseJSON } = require('../utils/common');
const { encryptString, decryptString } = require('../utils/encryption');
/**
* Sample secrets store file
@@ -29,7 +30,7 @@ class Oauth2Store {
// Get oauth2 data for all collections
getAllOauth2Data() {
let oauth2Data = this.store.get('credentials');
let oauth2Data = this.store.get('collections');
if (!Array.isArray(oauth2Data)) oauth2Data = [];
return oauth2Data;
}
@@ -45,7 +46,7 @@ class Oauth2Store {
collectionUid
};
let updatedOauth2Data = [...oauth2Data, newOauth2DataForCollection];
this.store.set('credentials', updatedOauth2Data);
this.store.set('collections', updatedOauth2Data);
return newOauth2DataForCollection;
}
@@ -60,7 +61,7 @@ class Oauth2Store {
let updatedOauth2Data = oauth2Data.filter((d) => d.collectionUid !== collectionUid);
updatedOauth2Data.push({ ...data });
this.store.set('credentials', updatedOauth2Data);
this.store.set('collections', updatedOauth2Data);
}
// Create a new oauth2 Session Id for a collection
@@ -107,7 +108,7 @@ class Oauth2Store {
let updatedOauth2Data = oauth2Data.filter((d) => d.collectionUid !== collectionUid);
updatedOauth2Data.push({ ...oauth2DataForCollection });
this.store.set('credentials', updatedOauth2Data);
this.store.set('collections', updatedOauth2Data);
} catch (err) {
console.log('error while clearing the oauth2 session cache', err);
}
@@ -117,7 +118,8 @@ class Oauth2Store {
try {
let oauth2DataForCollection = this.getOauth2DataOfCollection({ collectionUid, url });
let credentials = oauth2DataForCollection?.credentials?.find(c => (c?.url == url) && (c?.credentialsId == credentialsId));
return credentials?.data;
let decryptedCredentialsData = safeParseJSON(decryptString(credentials?.data));
return decryptedCredentialsData;
} catch (err) {
console.log('error retrieving oauth2 credentials from cache', err);
}
@@ -125,12 +127,13 @@ class Oauth2Store {
updateCredentialsForCollection({ collectionUid, url, credentialsId, credentials = {} }) {
try {
let encryptedCredentialsData = encryptString(safeStringifyJSON(credentials));
let oauth2DataForCollection = this.getOauth2DataOfCollection({ collectionUid, url });
let filteredCredentials = oauth2DataForCollection?.credentials?.filter(c => (c?.url !== url) || (c?.credentialsId !== credentialsId));
if (!filteredCredentials) filteredCredentials = [];
filteredCredentials.push({
url,
data: credentials,
data: encryptedCredentialsData,
credentialsId
});
let newOauth2DataForCollection = {

View File

@@ -218,6 +218,7 @@ const getOAuth2TokenUsingAuthorizationCode = async ({ request, collectionUid, fo
return { collectionUid, url, credentials: parsedResponseData, credentialsId, debugInfo };
} catch (error) {
console.log("auth code request failed", error);
return Promise.reject(safeStringifyJSON(error?.response?.data));
}
};
@@ -252,6 +253,7 @@ const getOAuth2AuthorizationCode = (request, codeChallenge, collectionUid) => {
});
resolve({ authorizationCode, debugInfo });
} catch (err) {
console.log("auth code block failed", err);
reject(err);
}
});
@@ -604,7 +606,7 @@ const getOAuth2TokenUsingPasswordCredentials = async ({ request, collectionUid,
const refreshOauth2Token = async (requestCopy, collectionUid) => {
const oAuth = get(requestCopy, 'oauth2', {});
const { clientId, clientSecret, credentialsId } = oAuth;
const url = oAuth.refreshUrl ? oAuth.refreshUrl : oAuth.accessTokenUrl;
const url = oAuth.refreshTokenUrl ? oAuth.refreshTokenUrl : oAuth.accessTokenUrl;
const credentials = getStoredOauth2Credentials({ collectionUid, url, credentialsId });
if (!credentials?.refresh_token) {

View File

@@ -88,7 +88,7 @@ function createTimelineAgentClass(BaseAgentClass) {
return class extends BaseAgentClass {
constructor(options, timeline) {
super(options);
this.timeline = timeline;
this.timeline = Array.isArray(timeline) ? timeline : [];
this.alpnProtocols = options.ALPNProtocols || ['h2', 'http/1.1'];
this.caProvided = !!options.ca;
}

View File

@@ -514,7 +514,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
const callbackUrlKey = _.find(auth, { name: 'callback_url' });
const authorizationUrlKey = _.find(auth, { name: 'authorization_url' });
const accessTokenUrlKey = _.find(auth, { name: 'access_token_url' });
const refreshUrlKey = _.find(auth, { name: 'refresh_url' });
const refreshTokenUrlKey = _.find(auth, { name: 'refresh_token_url' });
const clientIdKey = _.find(auth, { name: 'client_id' });
const clientSecretKey = _.find(auth, { name: 'client_secret' });
const scopeKey = _.find(auth, { name: 'scope' });
@@ -534,7 +534,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
? {
grantType: grantTypeKey ? grantTypeKey.value : '',
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
refreshUrl: refreshUrlKey ? refreshUrlKey.value : '',
refreshTokenUrl: refreshTokenUrlKey ? refreshTokenUrlKey.value : '',
username: usernameKey ? usernameKey.value : '',
password: passwordKey ? passwordKey.value : '',
clientId: clientIdKey ? clientIdKey.value : '',
@@ -554,7 +554,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '',
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
refreshUrl: refreshUrlKey ? refreshUrlKey.value : '',
refreshTokenUrl: refreshTokenUrlKey ? refreshTokenUrlKey.value : '',
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
@@ -572,7 +572,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
? {
grantType: grantTypeKey ? grantTypeKey.value : '',
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
refreshUrl: refreshUrlKey ? refreshUrlKey.value : '',
refreshTokenUrl: refreshTokenUrlKey ? refreshTokenUrlKey.value : '',
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',

View File

@@ -274,7 +274,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
const callbackUrlKey = _.find(auth, { name: 'callback_url' });
const authorizationUrlKey = _.find(auth, { name: 'authorization_url' });
const accessTokenUrlKey = _.find(auth, { name: 'access_token_url' });
const refreshUrlKey = _.find(auth, { name: 'refresh_url' });
const refreshTokenUrlKey = _.find(auth, { name: 'refresh_token_url' });
const clientIdKey = _.find(auth, { name: 'client_id' });
const clientSecretKey = _.find(auth, { name: 'client_secret' });
const scopeKey = _.find(auth, { name: 'scope' });
@@ -294,7 +294,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
? {
grantType: grantTypeKey ? grantTypeKey.value : '',
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
refreshUrl: refreshUrlKey ? refreshUrlKey.value : '',
refreshTokenUrl: refreshTokenUrlKey ? refreshTokenUrlKey.value : '',
username: usernameKey ? usernameKey.value : '',
password: passwordKey ? passwordKey.value : '',
clientId: clientIdKey ? clientIdKey.value : '',
@@ -314,7 +314,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
callbackUrl: callbackUrlKey ? callbackUrlKey.value : '',
authorizationUrl: authorizationUrlKey ? authorizationUrlKey.value : '',
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
refreshUrl: refreshUrlKey ? refreshUrlKey.value : '',
refreshTokenUrl: refreshTokenUrlKey ? refreshTokenUrlKey.value : '',
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',
@@ -332,7 +332,7 @@ const sem = grammar.createSemantics().addAttribute('ast', {
? {
grantType: grantTypeKey ? grantTypeKey.value : '',
accessTokenUrl: accessTokenUrlKey ? accessTokenUrlKey.value : '',
refreshUrl: refreshUrlKey ? refreshUrlKey.value : '',
refreshTokenUrl: refreshTokenUrlKey ? refreshTokenUrlKey.value : '',
clientId: clientIdKey ? clientIdKey.value : '',
clientSecret: clientSecretKey ? clientSecretKey.value : '',
scope: scopeKey ? scopeKey.value : '',

View File

@@ -183,7 +183,7 @@ ${indentString(`domain: ${auth?.ntlm?.domain || ''}`)}
bru += `auth:oauth2 {
${indentString(`grant_type: password`)}
${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)}
${indentString(`refresh_token_url: ${auth?.oauth2?.refreshTokenUrl || ''}`)}
${indentString(`username: ${auth?.oauth2?.username || ''}`)}
${indentString(`password: ${auth?.oauth2?.password || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
@@ -208,7 +208,7 @@ ${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(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)}
${indentString(`refresh_token_url: ${auth?.oauth2?.refreshTokenUrl || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
@@ -231,7 +231,7 @@ ${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken || false).
bru += `auth:oauth2 {
${indentString(`grant_type: client_credentials`)}
${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)}
${indentString(`refresh_token_url: ${auth?.oauth2?.refreshTokenUrl || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}

View File

@@ -149,7 +149,7 @@ ${indentString(`placement: ${auth?.apikey?.placement || ''}`)}
bru += `auth:oauth2 {
${indentString(`grant_type: password`)}
${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)}
${indentString(`refresh_token_url: ${auth?.oauth2?.refreshTokenUrl || ''}`)}
${indentString(`username: ${auth?.oauth2?.username || ''}`)}
${indentString(`password: ${auth?.oauth2?.password || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
@@ -174,7 +174,7 @@ ${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(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)}
${indentString(`refresh_token_url: ${auth?.oauth2?.refreshTokenUrl || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}
@@ -197,7 +197,7 @@ ${indentString(`auto_refresh_token: ${(auth?.oauth2?.autoRefreshToken).toString(
bru += `auth:oauth2 {
${indentString(`grant_type: client_credentials`)}
${indentString(`access_token_url: ${auth?.oauth2?.accessTokenUrl || ''}`)}
${indentString(`refresh_url: ${auth?.oauth2?.refreshUrl || ''}`)}
${indentString(`refresh_token_url: ${auth?.oauth2?.refreshTokenUrl || ''}`)}
${indentString(`client_id: ${auth?.oauth2?.clientId || ''}`)}
${indentString(`client_secret: ${auth?.oauth2?.clientSecret || ''}`)}
${indentString(`scope: ${auth?.oauth2?.scope || ''}`)}

View File

@@ -59,7 +59,7 @@ auth:oauth2 {
callback_url: http://localhost:8080/api/auth/oauth2/authorization_code/callback
authorization_url: http://localhost:8080/api/auth/oauth2/authorization_code/authorize
access_token_url: http://localhost:8080/api/auth/oauth2/authorization_code/token
refresh_url:
refresh_token_url:
client_id: client_id_1
client_secret: client_secret_1
scope: read write

View File

@@ -85,7 +85,7 @@
"credentialsPlacement": "body",
"grantType": "authorization_code",
"pkce": false,
"refreshUrl": "",
"refreshTokenUrl": "",
"scope": "read write",
"state": "807061d5f0be",
"tokenHeaderPrefix": "Bearer",

View File

@@ -238,7 +238,7 @@ const oauth2Schema = Yup.object({
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().strip()
}),
refreshUrl: Yup.string().when('grantType', {
refreshTokenUrl: Yup.string().when('grantType', {
is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val),
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().strip()

View File

@@ -15,7 +15,7 @@ auth:oauth2 {
callback_url: {{key-host}}/realms/bruno/account
authorization_url: {{key-host}}/realms/bruno/protocol/openid-connect/auth
access_token_url: {{key-host}}/realms/bruno/protocol/openid-connect/token
refresh_url:
refresh_token_url:
client_id: account
client_secret: Lh3NkRikMZpO12rwSBwVimde9v89B5Rw
scope: openid