feat: safe mode updates

This commit is contained in:
Anoop M D 2024-08-11 16:11:12 +05:30
parent b2baa1e48d
commit 751c7aa16d
12 changed files with 119 additions and 102 deletions

View File

@ -5,11 +5,10 @@ import EnvironmentSelector from 'components/Environments/EnvironmentSelector';
import { addTab } from 'providers/ReduxStore/slices/tabs'; import { addTab } from 'providers/ReduxStore/slices/tabs';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import SecuritySettingsIcon from 'components/SecuritySettings/SecurityIconWithModal/index'; import JsSandboxMode from 'components/SecuritySettings/JsSandboxMode';
const CollectionToolBar = ({ collection }) => { const CollectionToolBar = ({ collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const appMode = collection?.securityConfig?.appMode;
const handleRun = () => { const handleRun = () => {
dispatch( dispatch(
@ -59,16 +58,8 @@ const CollectionToolBar = ({ collection }) => {
<span className="ml-2 mr-4 font-semibold">{collection?.name}</span> <span className="ml-2 mr-4 font-semibold">{collection?.name}</span>
</div> </div>
<div className="flex flex-1 items-center justify-end"> <div className="flex flex-1 items-center justify-end">
{appMode && (
<span
className={`mr-4 border border-slate-500 px-2 py-1 rounded-md text-xs cursor-pointer opacity-70 ${appMode}`}
onClick={viewSecuritySettings}
>
{appMode} mode
</span>
)}
<span className="mr-2"> <span className="mr-2">
<SecuritySettingsIcon collection={collection} /> <JsSandboxMode collection={collection} />
</span> </span>
<span className="mr-2"> <span className="mr-2">
<IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} /> <IconRun className="cursor-pointer" size={20} strokeWidth={1.5} onClick={handleRun} />

View File

@ -0,0 +1,16 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.safe-mode {
padding: 0.15rem 0.3rem;
color: ${(props) => props.theme.colors.text.green};
border: solid 1px ${(props) => props.theme.colors.text.green} !important;
}
.developer-mode {
padding: 0.15rem 0.3rem;
color: ${(props) => props.theme.colors.text.yellow};
border: solid 1px ${(props) => props.theme.colors.text.yellow} !important;
}
`;
export default StyledWrapper;

View File

@ -0,0 +1,45 @@
import { useDispatch } from 'react-redux';
import { IconShieldLock } from '@tabler/icons';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { uuid } from 'utils/common/index';
import JsSandboxModeModal from '../JsSandboxModeModal';
import StyledWrapper from './StyledWrapper';
const JsSandboxMode = ({ collection }) => {
const jsSandboxMode = collection?.securityConfig?.jsSandboxMode;
const dispatch = useDispatch();
const viewSecuritySettings = () => {
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'security-settings'
})
);
};
return (
<StyledWrapper className='flex'>
{jsSandboxMode === 'safe' && (
<div
className="flex items-center border rounded-md text-xs cursor-pointer safe-mode"
onClick={viewSecuritySettings}
>
Safe Mode
</div>
)}
{jsSandboxMode === 'developer' && (
<div
className="flex items-center border rounded-md text-xs cursor-pointer developer-mode"
onClick={viewSecuritySettings}
>
Developer Mode
</div>
)}
{!jsSandboxMode ? <JsSandboxModeModal collection={collection} /> : null}
</StyledWrapper>
);
};
export default JsSandboxMode;

View File

@ -6,33 +6,32 @@ import Portal from 'components/Portal';
import Modal from 'components/Modal'; import Modal from 'components/Modal';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const AppModeModal = ({ collection, onClose }) => { const JsSandboxModeModal = ({ collection, onClose }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [selectedAppMode, setSelectedAppMode] = useState(collection?.securityConfig?.appMode || 'developer'); const [jsSandboxMode, setJsSandboxMode] = useState(collection?.securityConfig?.jsSandboxMode || 'safe');
const handleAppModeChange = (e) => { const handleChange = (e) => {
setSelectedAppMode(e.target.value); setJsSandboxMode(e.target.value);
}; };
const handleSave = () => { const handleSave = () => {
dispatch( dispatch(
saveCollectionSecurityConfig(collection?.uid, { saveCollectionSecurityConfig(collection?.uid, {
appMode: selectedAppMode, jsSandboxMode: jsSandboxMode
runtime: selectedAppMode === 'developer' ? 'vm2' : selectedAppMode === 'safe' ? 'isolated-vm' : undefined
}) })
) )
.then(() => { .then(() => {
toast.success('App Mode updated successfully'); toast.success('Sandbox mode updated successfully');
onClose(); onClose();
}) })
.catch((err) => console.log(err) && toast.error('Failed to update JS AppMode')); .catch((err) => console.log(err) && toast.error('Failed to update sandbox mode'));
}; };
return ( return (
<Portal> <Portal>
<Modal <Modal
size="sm" size="sm"
title={'Scripting Sandbox'} title={'JavaScript Sandbox'}
confirmText="Save" confirmText="Save"
handleConfirm={handleSave} handleConfirm={handleSave}
hideCancel={true} hideCancel={true}
@ -54,13 +53,13 @@ const AppModeModal = ({ collection, onClose }) => {
<input <input
type="radio" type="radio"
id="safe" id="safe"
name="appMode" name="jsSandboxMode"
value="safe" value="safe"
checked={selectedAppMode === 'safe'} checked={jsSandboxMode === 'safe'}
onChange={handleAppModeChange} onChange={handleChange}
className="cursor-pointer" className="cursor-pointer"
/> />
<span className={selectedAppMode === 'safe' ? 'font-medium' : 'font-normal'}> <span className={jsSandboxMode === 'safe' ? 'font-medium' : 'font-normal'}>
Safe Mode Safe Mode
</span> </span>
<span className='beta-tag'>BETA</span> <span className='beta-tag'>BETA</span>
@ -73,13 +72,13 @@ const AppModeModal = ({ collection, onClose }) => {
<input <input
type="radio" type="radio"
id="developer" id="developer"
name="appMode" name="jsSandboxMode"
value="developer" value="developer"
checked={selectedAppMode === 'developer'} checked={jsSandboxMode === 'developer'}
onChange={handleAppModeChange} onChange={handleChange}
className="cursor-pointer" className="cursor-pointer"
/> />
<span className={selectedAppMode === 'developer' ? 'font-medium' : 'font-normal'}> <span className={jsSandboxMode === 'developer' ? 'font-medium' : 'font-normal'}>
Developer Mode Developer Mode
<span className='ml-1 developer-mode-warning'>(use only if you trust the collections authors)</span> <span className='ml-1 developer-mode-warning'>(use only if you trust the collections authors)</span>
</span> </span>
@ -97,4 +96,4 @@ const AppModeModal = ({ collection, onClose }) => {
); );
}; };
export default AppModeModal; export default JsSandboxModeModal;

View File

@ -1,35 +0,0 @@
import { setShowAppModeModal } from 'providers/ReduxStore/slices/collections/index';
import { useDispatch } from 'react-redux';
import { IconShieldLock } from '@tabler/icons';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { uuid } from 'utils/common/index';
import AppModeModal from './AppModeModal/index';
const SecuritySettingsIcon = ({ collection }) => {
const showAppModeModel = collection?.showAppModeModal;
const dispatch = useDispatch();
const viewSecuritySettings = () => {
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
type: 'security-settings'
})
);
};
return (
<>
<IconShieldLock className="cursor-pointer" size={20} strokeWidth={1.5} onClick={viewSecuritySettings} />
{showAppModeModel ? (
<AppModeModal
collection={collection}
onClose={() => dispatch(setShowAppModeModal({ showAppModeModal: false, collectionUid: collection?.uid }))}
/>
) : null}
</>
);
};
export default SecuritySettingsIcon;

View File

@ -1,33 +1,32 @@
import { useState } from 'react'; import { useState } from 'react';
import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions'; import { saveCollectionSecurityConfig } from 'providers/ReduxStore/slices/collections/actions';
import classnames from 'classnames'; import toast from 'react-hot-toast';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
const SecuritySettings = ({ collection }) => { const SecuritySettings = ({ collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [selectedAppMode, setSelectedAppMode] = useState(collection?.securityConfig?.appMode || 'developer'); const [jsSandboxMode, setJsSandboxMode] = useState(collection?.securityConfig?.jsSandboxMode || 'safe');
const handleAppModeChange = (e) => { const handleChange = (e) => {
setSelectedAppMode(e.target.value); setJsSandboxMode(e.target.value);
}; };
const handleSave = () => { const handleSave = () => {
dispatch( dispatch(
saveCollectionSecurityConfig(collection?.uid, { saveCollectionSecurityConfig(collection?.uid, {
appMode: selectedAppMode, jsSandboxMode: jsSandboxMode
runtime: selectedAppMode === 'developer' ? 'vm2' : selectedAppMode === 'safe' ? 'isolated-vm' : undefined
}) })
) )
.then(() => { .then(() => {
toast.success('App Mode updated successfully'); toast.success('Sandbox mode updated successfully');
}) })
.catch((err) => console.log(err) && toast.error('Failed to update JS AppMode')); .catch((err) => console.log(err) && toast.error('Failed to update sandbox mode'));
}; };
return ( return (
<StyledWrapper className="flex flex-col h-full relative px-4 py-4"> <StyledWrapper className="flex flex-col h-full relative px-4 py-4">
<div className='font-semibold mt-2'>Scripting Sandbox</div> <div className='font-semibold mt-2'>JavaScript Sandbox</div>
<div className='mt-4'> <div className='mt-4'>
The collection might include JavaScript code in Variables, Scripts, Tests, and Assertions. The collection might include JavaScript code in Variables, Scripts, Tests, and Assertions.
@ -39,13 +38,13 @@ const SecuritySettings = ({ collection }) => {
<input <input
type="radio" type="radio"
id="safe" id="safe"
name="appMode" name="jsSandboxMode"
value="safe" value="safe"
checked={selectedAppMode === 'safe'} checked={jsSandboxMode === 'safe'}
onChange={handleAppModeChange} onChange={handleChange}
className="cursor-pointer" className="cursor-pointer"
/> />
<span className={selectedAppMode === 'safe' ? 'font-medium' : 'font-normal'}> <span className={jsSandboxMode === 'safe' ? 'font-medium' : 'font-normal'}>
Safe Mode Safe Mode
</span> </span>
<span className='beta-tag'>BETA</span> <span className='beta-tag'>BETA</span>
@ -58,13 +57,13 @@ const SecuritySettings = ({ collection }) => {
<input <input
type="radio" type="radio"
id="developer" id="developer"
name="appMode" name="jsSandboxMode"
value="developer" value="developer"
checked={selectedAppMode === 'developer'} checked={jsSandboxMode === 'developer'}
onChange={handleAppModeChange} onChange={handleChange}
className="cursor-pointer" className="cursor-pointer"
/> />
<span className={selectedAppMode === 'developer' ? 'font-medium' : 'font-normal'}> <span className={jsSandboxMode === 'developer' ? 'font-medium' : 'font-normal'}>
Developer Mode Developer Mode
<span className='ml-1 developer-mode-warning'>(use only if you trust the collections authors)</span> <span className='ml-1 developer-mode-warning'>(use only if you trust the collections authors)</span>
</span> </span>
@ -76,6 +75,9 @@ const SecuritySettings = ({ collection }) => {
<button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit mt-6"> <button onClick={handleSave} className="submit btn btn-sm btn-secondary w-fit mt-6">
Save Save
</button> </button>
<small className='text-muted mt-6'>
* SAFE mode has been introduced v1.25 onwards and is in beta. Please report any issues on github.
</small>
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -33,9 +33,6 @@ export const collectionsSlice = createSlice({
const collection = action.payload; const collection = action.payload;
collection.settingsSelectedTab = 'headers'; collection.settingsSelectedTab = 'headers';
collection.showAppModeModal = !collection?.securityConfig?.appMode;
collection.folderLevelSettingsSelectedTab = {}; collection.folderLevelSettingsSelectedTab = {};
// TODO: move this to use the nextAction approach // TODO: move this to use the nextAction approach
@ -53,10 +50,6 @@ export const collectionsSlice = createSlice({
state.collections.push(collection); state.collections.push(collection);
} }
}, },
setShowAppModeModal: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
collection.showAppModeModal = action.payload.showAppModeModal;
},
setCollectionSecurityConfig: (state, action) => { setCollectionSecurityConfig: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid); const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) { if (collection) {
@ -1718,8 +1711,7 @@ export const {
runRequestEvent, runRequestEvent,
runFolderEvent, runFolderEvent,
resetCollectionRunner, resetCollectionRunner,
updateRequestDocs, updateRequestDocs
setShowAppModeModal
} = collectionsSlice.actions; } = collectionsSlice.actions;
export default collectionsSlice.reducer; export default collectionsSlice.reducer;

View File

@ -670,7 +670,9 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
ipcMain.handle('renderer:save-collection-security-config', async (event, collectionPath, securityConfig) => { ipcMain.handle('renderer:save-collection-security-config', async (event, collectionPath, securityConfig) => {
try { try {
collectionSecurityStore.setSecurityConfigForCollection(collectionPath, securityConfig); collectionSecurityStore.setSecurityConfigForCollection(collectionPath, {
jsSandboxMode: securityConfig.jsSandboxMode
});
} catch (error) { } catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }

View File

@ -81,6 +81,11 @@ const getEnvVars = (environment = {}) => {
}; };
}; };
const getJsSandboxRuntime = (collection) => {
const securityConfig = get(collection, 'securityConfig', {});
return securityConfig.jsSandboxMode === 'safe' ? 'isolated-vm' : 'vm2';
};
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/; const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
const configureRequest = async ( const configureRequest = async (
@ -459,7 +464,8 @@ const registerNetworkIpc = (mainWindow) => {
const envVars = getEnvVars(environment); const envVars = getEnvVars(environment);
const processEnvVars = getProcessEnvVars(collectionUid); const processEnvVars = getProcessEnvVars(collectionUid);
const brunoConfig = getBrunoConfig(collectionUid); const brunoConfig = getBrunoConfig(collectionUid);
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) }; const scriptingConfig = get(brunoConfig, 'scripts', {});
scriptingConfig.runtime = getJsSandboxRuntime(collection);
try { try {
const controller = new AbortController(); const controller = new AbortController();
@ -660,7 +666,8 @@ const registerNetworkIpc = (mainWindow) => {
const envVars = getEnvVars(environment); const envVars = getEnvVars(environment);
const processEnvVars = getProcessEnvVars(collectionUid); const processEnvVars = getProcessEnvVars(collectionUid);
const brunoConfig = getBrunoConfig(collectionUid); const brunoConfig = getBrunoConfig(collectionUid);
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) }; const scriptingConfig = get(brunoConfig, 'scripts', {});
scriptingConfig.runtime = getJsSandboxRuntime(collection);
await runPreRequest( await runPreRequest(
request, request,
@ -765,7 +772,8 @@ const registerNetworkIpc = (mainWindow) => {
const runtimeVariables = collection.runtimeVariables; const runtimeVariables = collection.runtimeVariables;
const processEnvVars = getProcessEnvVars(collectionUid); const processEnvVars = getProcessEnvVars(collectionUid);
const brunoConfig = getBrunoConfig(collection.uid); const brunoConfig = getBrunoConfig(collection.uid);
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) }; const scriptingConfig = get(brunoConfig, 'scripts', {});
scriptingConfig.runtime = getJsSandboxRuntime(collection);
await runPreRequest( await runPreRequest(
request, request,
@ -831,7 +839,8 @@ const registerNetworkIpc = (mainWindow) => {
const folderUid = folder ? folder.uid : null; const folderUid = folder ? folder.uid : null;
const cancelTokenUid = uuid(); const cancelTokenUid = uuid();
const brunoConfig = getBrunoConfig(collectionUid); const brunoConfig = getBrunoConfig(collectionUid);
const scriptingConfig = { ...get(brunoConfig, 'scripts', {}), ...get(collection, 'securityConfig', {}) }; const scriptingConfig = get(brunoConfig, 'scripts', {});
scriptingConfig.runtime = getJsSandboxRuntime(collection);
const collectionRoot = get(collection, 'root', {}); const collectionRoot = get(collection, 'root', {});
const abortController = new AbortController(); const abortController = new AbortController();

View File

@ -16,7 +16,9 @@ class CollectionSecurityStore {
if (!collection) { if (!collection) {
collections.push({ collections.push({
path: collectionPathname, path: collectionPathname,
securityConfig securityConfig: {
jsSandboxMode: securityConfig.jsSandboxMode
}
}); });
this.store.set('collections', collections); this.store.set('collections', collections);

View File

@ -6,9 +6,6 @@ const { terser } = require('rollup-plugin-terser');
const bundleLibraries = async () => { const bundleLibraries = async () => {
const codeScript = ` const codeScript = `
import isNumber from "is-number";
global.isNumber = isNumber;
import { faker } from "@faker-js/faker";
import { expect, assert } from 'chai'; import { expect, assert } from 'chai';
import { Buffer } from "buffer"; import { Buffer } from "buffer";
import moment from "moment"; import moment from "moment";
@ -16,15 +13,12 @@ const bundleLibraries = async () => {
import atob from "atob"; import atob from "atob";
global.expect = expect; global.expect = expect;
global.assert = assert; global.assert = assert;
global.faker = faker;
global.moment = moment; global.moment = moment;
global.btoa = btoa; global.btoa = btoa;
global.atob = atob; global.atob = atob;
global.Buffer = Buffer; global.Buffer = Buffer;
global.requireObject = { global.requireObject = {
'chai': { expect, assert }, 'chai': { expect, assert },
'faker': faker,
'@faker-js/faker': { faker },
'moment': moment, 'moment': moment,
'buffer': { Buffer }, 'buffer': { Buffer },
'btoa': btoa, 'btoa': btoa,