diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js new file mode 100644 index 000000000..bc5ad1565 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/StyledWrapper.js @@ -0,0 +1,31 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .settings-label { + width: 80px; + } + + input { + width: 300px; + } + + .textbox { + border: 1px solid #ccc; + padding: 0.15rem 0.45rem; + box-shadow: none; + border-radius: 0px; + outline: none; + box-shadow: none; + transition: border-color ease-in-out 0.1s; + border-radius: 3px; + background-color: ${(props) => props.theme.modal.input.bg}; + border: 1px solid ${(props) => props.theme.modal.input.border}; + + &:focus { + border: solid 1px ${(props) => props.theme.modal.input.focusBorder} !important; + outline: none !important; + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js new file mode 100644 index 000000000..280e0f4bb --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/ClientCertSettings/index.js @@ -0,0 +1,120 @@ +import React, { useEffect } from 'react'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; + +import StyledWrapper from './StyledWrapper'; + +const ClientCertSettings = ({ clientCertConfig, onUpdate, onRemove }) => { + const formik = useFormik({ + initialValues: { + domain: '', + certFilePath: '', + keyFilePath: '', + passphrase: '' + }, + validationSchema: Yup.object({ + domain: Yup.string().required(), + certFilePath: Yup.string().required(), + keyFilePath: Yup.string().required(), + passphrase: Yup.string() + }), + onSubmit: (values) => { + onUpdate(values); + } + }); + + const getFile = (e) => { + formik.values[e.name] = e.files[0].path; + }; + + return ( + +

Current client certificates

+ +

New client certicate

+
+
+ + + {formik.touched.domain && formik.errors.domain ? ( +
{formik.errors.domain}
+ ) : null} +
+
+ + getFile(e.target)} + /> + {formik.touched.certFilePath && formik.errors.certFilePath ? ( +
{formik.errors.certFilePath}
+ ) : null} +
+
+ + getFile(e.target)} + /> + {formik.touched.keyFilePath && formik.errors.keyFilePath ? ( +
{formik.errors.keyFilePath}
+ ) : null} +
+
+ + + {formik.touched.passphrase && formik.errors.passphrase ? ( +
{formik.errors.passphrase}
+ ) : null} +
+
+ +
+
+
+ ); +}; + +export default ClientCertSettings; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index b3a1ece81..90b3af9a4 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -7,6 +7,7 @@ import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actio import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections'; import { useDispatch } from 'react-redux'; import ProxySettings from './ProxySettings'; +import ClientCertSettings from './ClientCertSettings'; import Headers from './Headers'; import Auth from './Auth'; import Script from './Script'; @@ -28,6 +29,8 @@ const CollectionSettings = ({ collection }) => { const proxyConfig = get(collection, 'brunoConfig.proxy', {}); + const clientCertConfig = get(collection, 'brunoConfig.clientCertificates', []); + const onProxySettingsUpdate = (config) => { const brunoConfig = cloneDeep(collection.brunoConfig); brunoConfig.proxy = config; @@ -38,6 +41,28 @@ const CollectionSettings = ({ collection }) => { .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); }; + const onClientCertSettingsUpdate = (config) => { + const brunoConfig = cloneDeep(collection.brunoConfig); + brunoConfig.clientCertificates + ? brunoConfig.clientCertificates.push(config) + : (brunoConfig.clientCertificates = [config]); + dispatch(updateBrunoConfig(brunoConfig, collection.uid)) + .then(() => { + toast.success('Collection settings updated successfully'); + }) + .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); + }; + + const onClientCertSettingsRemove = (config) => { + const brunoConfig = cloneDeep(collection.brunoConfig); + brunoConfig.clientCertificates = brunoConfig.clientCertificates.filter((item) => item.domain != config.domain); + dispatch(updateBrunoConfig(brunoConfig, collection.uid)) + .then(() => { + toast.success('Collection settings updated successfully'); + }) + .catch((err) => console.log(err) && toast.error('Failed to update collection settings')); + }; + const getTabPanel = (tab) => { switch (tab) { case 'headers': { @@ -55,6 +80,15 @@ const CollectionSettings = ({ collection }) => { case 'proxy': { return ; } + case 'clientCert': { + return ( + + ); + } case 'docs': { return ; } @@ -85,6 +119,9 @@ const CollectionSettings = ({ collection }) => {
setTab('proxy')}> Proxy
+
setTab('clientCert')}> + Client certificate +
setTab('docs')}> Docs
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 8e8cb6247..9d308faf7 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -1,4 +1,5 @@ const os = require('os'); +const fs = require('fs'); const qs = require('qs'); const https = require('https'); const axios = require('axios'); @@ -214,7 +215,6 @@ const registerNetworkIpc = (mainWindow) => { cacertFile = cacertArray.find((el) => el); if (cacertFile && cacertFile.length > 1) { try { - const fs = require('fs'); caCrt = fs.readFileSync(cacertFile); httpsAgentRequestFields['ca'] = caCrt; } catch (err) { @@ -223,18 +223,41 @@ const registerNetworkIpc = (mainWindow) => { } } - // proxy configuration const brunoConfig = getBrunoConfig(collectionUid); + const interpolationOptions = { + envVars, + collectionVariables, + processEnvVars + }; + + // client certificate config + const clientCertConfig = get(brunoConfig, 'clientCertificates', []); + + for (clientCert of clientCertConfig) { + const domain = interpolateString(clientCert.domain, interpolationOptions); + const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); + const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); + if (domain && certFilePath && keyFilePath) { + const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*'); + + if (request.url.match(hostRegex)) { + try { + httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath); + httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath); + } catch (err) { + console.log('Error reading cert/key file', err); + } + httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions); + break; + } + } + } + + // proxy configuration const proxyEnabled = get(brunoConfig, 'proxy.enabled', false); if (proxyEnabled) { let proxyUri; - const interpolationOptions = { - envVars, - collectionVariables, - processEnvVars - }; - const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions); const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions); const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);