diff --git a/packages/bruno-electron/src/ipc/network/digestauth-helper.js b/packages/bruno-electron/src/ipc/network/digestauth-helper.js index b1dbf7c7b..f01ba86df 100644 --- a/packages/bruno-electron/src/ipc/network/digestauth-helper.js +++ b/packages/bruno-electron/src/ipc/network/digestauth-helper.js @@ -2,7 +2,7 @@ const crypto = require('crypto'); const { URL } = require('url'); function isStrPresent(str) { - return str && str !== '' && str !== 'undefined'; + return str && str.trim() !== '' && str.trim() !== 'undefined'; } function stripQuotes(str) { @@ -15,7 +15,10 @@ function containsDigestHeader(response) { } function containsAuthorizationHeader(originalRequest) { - return Boolean(originalRequest.headers['Authorization']); + return Boolean( + originalRequest.headers['Authorization'] || + originalRequest.headers['authorization'] + ); } function md5(input) { @@ -24,10 +27,10 @@ function md5(input) { function addDigestInterceptor(axiosInstance, request) { const { username, password } = request.digestConfig; - console.debug(request); + console.debug('Digest Auth Interceptor Initialized'); if (!isStrPresent(username) || !isStrPresent(password)) { - console.warn('Required Digest Auth fields are not present'); + console.warn('Required Digest Auth fields (username/password) are not present'); return; } @@ -36,23 +39,37 @@ function addDigestInterceptor(axiosInstance, request) { (error) => { const originalRequest = error.config; + // Prevent retry loops + if (originalRequest._retry) { + return Promise.reject(error); + } + originalRequest._retry = true; + if ( error.response?.status === 401 && containsDigestHeader(error.response) && !containsAuthorizationHeader(originalRequest) ) { + console.debug('Processing Digest Authentication Challenge'); console.debug(error.response.headers['www-authenticate']); const authDetails = error.response.headers['www-authenticate'] .split(',') .map((pair) => pair.split('=').map((item) => item.trim()).map(stripQuotes)) .reduce((acc, [key, value]) => { - if (key && value !== undefined) { - acc[key] = value; + const normalizedKey = key.toLowerCase().replace('digest ', ''); + if (normalizedKey && value !== undefined) { + acc[normalizedKey] = value; } return acc; }, {}); + // Validate required auth details + if (!authDetails.realm || !authDetails.nonce) { + console.warn('Missing required auth details (realm or nonce)'); + return Promise.reject(error); + } + console.debug("Auth Details: \n", authDetails); const nonceCount = '00000001'; @@ -65,20 +82,35 @@ function addDigestInterceptor(axiosInstance, request) { authDetails.algorithm = 'MD5'; } - const uri = new URL(request.url).pathname; - const HA1 = md5(`${username}:${authDetails['Digest realm']}:${password}`); + const uri = new URL(request.url, request.baseURL || 'http://localhost').pathname; // Handle relative URLs + const HA1 = md5(`${username}:${authDetails.realm}:${password}`); const HA2 = md5(`${request.method}:${uri}`); const response = md5( `${HA1}:${authDetails.nonce}:${nonceCount}:${cnonce}:auth:${HA2}` ); - const authorizationHeader = - `Digest username="${username}",realm="${authDetails.realm}",` + - `nonce="${authDetails.nonce}",uri="${uri}",qop="auth",algorithm="${authDetails.algorithm}",` + - `response="${response}",nc="${nonceCount}",cnonce="${cnonce}"` + - (authDetails.opaque ? `,opaque="${authDetails.opaque}"` : ''); + const headerFields = [ + `username="${username}"`, + `realm="${authDetails.realm}"`, + `nonce="${authDetails.nonce}"`, + `uri="${uri}"`, + `qop="auth"`, + `algorithm="${authDetails.algorithm}"`, + `response="${response}"`, + `nc="${nonceCount}"`, + `cnonce="${cnonce}"`, + ]; + if (authDetails.opaque) { + headerFields.push(`opaque="${authDetails.opaque}"`); + } + + const authorizationHeader = `Digest ${headerFields.join(', ')}`; + + // Ensure headers are initialized + originalRequest.headers = originalRequest.headers || {}; originalRequest.headers['Authorization'] = authorizationHeader; + console.debug(`Authorization: ${originalRequest.headers['Authorization']}`); delete originalRequest.digestConfig; diff --git a/packages/bruno-tests/collection/auth/digest auth/simple digest auth.bru b/packages/bruno-tests/collection/auth/digest auth/simple digest auth.bru new file mode 100644 index 000000000..b0d41b2e8 --- /dev/null +++ b/packages/bruno-tests/collection/auth/digest auth/simple digest auth.bru @@ -0,0 +1,20 @@ +meta { + name: simple digest auth + type: http + seq: 1 +} + +get { + url: https://httpbin.org/digest-auth/qop/one/two + body: none + auth: digest +} + +headers { + accept: application/json +} + +auth:digest { + username: one + password: two +}