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
+
+ {!clientCertConfig.length
+ ? 'None'
+ : clientCertConfig.map((clientCert) => (
+ -
+ Domain: {clientCert.domain}
+
+
+ ))}
+
+ New client certicate
+
+
+ );
+};
+
+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);