-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #817 from drinkbird/feature/digestauth
Add Digest Auth Support #119
- Loading branch information
Showing
25 changed files
with
401 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/StyledWrapper.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import styled from 'styled-components'; | ||
|
||
const Wrapper = styled.div` | ||
label { | ||
font-size: 0.8125rem; | ||
} | ||
.single-line-editor-wrapper { | ||
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; |
71 changes: 71 additions & 0 deletions
71
packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
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 { updateCollectionAuth } from 'providers/ReduxStore/slices/collections'; | ||
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; | ||
import StyledWrapper from './StyledWrapper'; | ||
|
||
const DigestAuth = ({ collection }) => { | ||
const dispatch = useDispatch(); | ||
const { storedTheme } = useTheme(); | ||
|
||
const digestAuth = get(collection, 'root.request.auth.digest', {}); | ||
|
||
const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); | ||
|
||
const handleUsernameChange = (username) => { | ||
dispatch( | ||
updateCollectionAuth({ | ||
mode: 'digest', | ||
collectionUid: collection.uid, | ||
content: { | ||
username: username, | ||
password: digestAuth.password | ||
} | ||
}) | ||
); | ||
}; | ||
|
||
const handlePasswordChange = (password) => { | ||
dispatch( | ||
updateCollectionAuth({ | ||
mode: 'digest', | ||
collectionUid: collection.uid, | ||
content: { | ||
username: digestAuth.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={digestAuth.username || ''} | ||
theme={storedTheme} | ||
onSave={handleSave} | ||
onChange={(val) => handleUsernameChange(val)} | ||
collection={collection} | ||
/> | ||
</div> | ||
|
||
<label className="block font-medium mb-2">Password</label> | ||
<div className="single-line-editor-wrapper"> | ||
<SingleLineEditor | ||
value={digestAuth.password || ''} | ||
theme={storedTheme} | ||
onSave={handleSave} | ||
onChange={(val) => handlePasswordChange(val)} | ||
collection={collection} | ||
/> | ||
</div> | ||
</StyledWrapper> | ||
); | ||
}; | ||
|
||
export default DigestAuth; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/StyledWrapper.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import styled from 'styled-components'; | ||
|
||
const Wrapper = styled.div` | ||
label { | ||
font-size: 0.8125rem; | ||
} | ||
.single-line-editor-wrapper { | ||
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; |
76 changes: 76 additions & 0 deletions
76
packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
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 DigestAuth = ({ item, collection }) => { | ||
const dispatch = useDispatch(); | ||
const { storedTheme } = useTheme(); | ||
|
||
const digestAuth = item.draft ? get(item, 'draft.request.auth.digest', {}) : get(item, 'request.auth.digest', {}); | ||
|
||
const handleRun = () => dispatch(sendRequest(item, collection.uid)); | ||
const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); | ||
|
||
const handleUsernameChange = (username) => { | ||
dispatch( | ||
updateAuth({ | ||
mode: 'digest', | ||
collectionUid: collection.uid, | ||
itemUid: item.uid, | ||
content: { | ||
username: username, | ||
password: digestAuth.password | ||
} | ||
}) | ||
); | ||
}; | ||
|
||
const handlePasswordChange = (password) => { | ||
dispatch( | ||
updateAuth({ | ||
mode: 'digest', | ||
collectionUid: collection.uid, | ||
itemUid: item.uid, | ||
content: { | ||
username: digestAuth.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={digestAuth.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={digestAuth.password || ''} | ||
theme={storedTheme} | ||
onSave={handleSave} | ||
onChange={(val) => handlePasswordChange(val)} | ||
onRun={handleRun} | ||
collection={collection} | ||
/> | ||
</div> | ||
</StyledWrapper> | ||
); | ||
}; | ||
|
||
export default DigestAuth; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
packages/bruno-electron/src/ipc/network/digestauth-helper.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
const crypto = require('crypto'); | ||
|
||
function isStrPresent(str) { | ||
return str && str !== '' && str !== 'undefined'; | ||
} | ||
|
||
function stripQuotes(str) { | ||
return str.replace(/"/g, ''); | ||
} | ||
|
||
function containsDigestHeader(response) { | ||
const authHeader = response?.headers?.['www-authenticate']; | ||
return authHeader ? authHeader.trim().toLowerCase().startsWith('digest') : false; | ||
} | ||
|
||
function containsAuthorizationHeader(originalRequest) { | ||
return Boolean(originalRequest.headers['Authorization']); | ||
} | ||
|
||
function md5(input) { | ||
return crypto.createHash('md5').update(input).digest('hex'); | ||
} | ||
|
||
function addDigestInterceptor(axiosInstance, request) { | ||
const { username, password } = request.digestConfig; | ||
|
||
console.debug(request); | ||
|
||
if (!isStrPresent(username) || !isStrPresent(password)) { | ||
console.warn('Required Digest Auth fields are not present'); | ||
return; | ||
} | ||
|
||
axiosInstance.interceptors.response.use( | ||
(response) => response, | ||
(error) => { | ||
const originalRequest = error.config; | ||
|
||
if ( | ||
error.response?.status === 401 && | ||
containsDigestHeader(error.response) && | ||
!containsAuthorizationHeader(originalRequest) | ||
) { | ||
console.debug(error.response.headers['www-authenticate']); | ||
|
||
const authDetails = error.response.headers['www-authenticate'] | ||
.split(', ') | ||
.map((v) => v.split('=').map(stripQuotes)) | ||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); | ||
console.debug(authDetails); | ||
|
||
const nonceCount = '00000001'; | ||
const cnonce = crypto.randomBytes(24).toString('hex'); | ||
|
||
if (authDetails.algorithm.toUpperCase() !== 'MD5') { | ||
console.warn(`Unsupported Digest algorithm: ${algo}`); | ||
return Promise.reject(error); | ||
} | ||
const HA1 = md5(`${username}:${authDetails['Digest realm']}:${password}`); | ||
const HA2 = md5(`${request.method}:${request.url}`); | ||
const response = md5(`${HA1}:${authDetails.nonce}:${nonceCount}:${cnonce}:auth:${HA2}`); | ||
|
||
const authorizationHeader = | ||
`Digest username="${username}",realm="${authDetails['Digest realm']}",` + | ||
`nonce="${authDetails.nonce}",uri="${request.url}",qop="auth",algorithm="${authDetails.algorithm}",` + | ||
`response="${response}",nc="${nonceCount}",cnonce="${cnonce}"`; | ||
originalRequest.headers['Authorization'] = authorizationHeader; | ||
console.debug(`Authorization: ${originalRequest.headers['Authorization']}`); | ||
|
||
delete originalRequest.digestConfig; | ||
return axiosInstance(originalRequest); | ||
} | ||
|
||
return Promise.reject(error); | ||
} | ||
); | ||
} | ||
|
||
module.exports = { addDigestInterceptor }; |
Oops, something went wrong.