mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-09 06:55:03 +00:00
175 lines
5.2 KiB
JavaScript
175 lines
5.2 KiB
JavaScript
const { fromIni } = require('@aws-sdk/credential-providers');
|
|
const { aws4Interceptor } = require('aws4-axios');
|
|
|
|
async function resolveAwsV4Credentials(request) {
|
|
const awsv4 = request.awsv4config;
|
|
if (isStrPresent(awsv4.profileName)) {
|
|
try {
|
|
credentialsProvider = fromIni({
|
|
profile: awsv4.profileName
|
|
});
|
|
credentials = await credentialsProvider();
|
|
awsv4.accessKeyId = credentials.accessKeyId;
|
|
awsv4.secretAccessKey = credentials.secretAccessKey;
|
|
awsv4.sessionToken = credentials.sessionToken;
|
|
} catch {
|
|
console.error('Failed to fetch credentials from AWS profile.');
|
|
}
|
|
}
|
|
return awsv4;
|
|
}
|
|
|
|
function addAwsV4Interceptor(axiosInstance, request) {
|
|
if (!request.awsv4config) {
|
|
console.warn('No Auth Config found!');
|
|
return;
|
|
}
|
|
|
|
const awsv4 = request.awsv4config;
|
|
if (!isStrPresent(awsv4.accessKeyId) || !isStrPresent(awsv4.secretAccessKey)) {
|
|
console.warn('Required Auth Fields are not present');
|
|
return;
|
|
}
|
|
|
|
const interceptor = aws4Interceptor({
|
|
options: {
|
|
region: awsv4.region,
|
|
service: awsv4.service
|
|
},
|
|
credentials: {
|
|
accessKeyId: awsv4.accessKeyId,
|
|
secretAccessKey: awsv4.secretAccessKey,
|
|
sessionToken: awsv4.sessionToken
|
|
}
|
|
});
|
|
|
|
axiosInstance.interceptors.request.use(interceptor);
|
|
}
|
|
|
|
function addDigestInterceptor(axiosInstance, request) {
|
|
const { username, password } = request.digestConfig;
|
|
console.debug('Digest Auth Interceptor Initialized');
|
|
|
|
if (!isStrPresent(username) || !isStrPresent(password)) {
|
|
console.warn('Required Digest Auth fields (username/password) are not present');
|
|
return;
|
|
}
|
|
|
|
axiosInstance.interceptors.response.use(
|
|
(response) => response,
|
|
(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]) => {
|
|
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';
|
|
const cnonce = crypto.randomBytes(24).toString('hex');
|
|
|
|
if (authDetails.algorithm && authDetails.algorithm.toUpperCase() !== 'MD5') {
|
|
console.warn(`Unsupported Digest algorithm: ${authDetails.algorithm}`);
|
|
return Promise.reject(error);
|
|
} else {
|
|
authDetails.algorithm = 'MD5';
|
|
}
|
|
|
|
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 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;
|
|
|
|
return axiosInstance(originalRequest);
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
}
|
|
|
|
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'] ||
|
|
originalRequest.headers['authorization']
|
|
);
|
|
}
|
|
|
|
function md5(input) {
|
|
return crypto.createHash('md5').update(input).digest('hex');
|
|
}
|
|
|
|
function isStrPresent(str) {
|
|
return str && str !== '' && str !== 'undefined';
|
|
}
|
|
|
|
function stripQuotes(str) {
|
|
return str.replace(/"/g, '');
|
|
}
|
|
|
|
module.exports = {
|
|
addAwsV4Interceptor,
|
|
resolveAwsV4Credentials,
|
|
addDigestInterceptor
|
|
} |