I resolved this issue with the below-mentioned code:
import axios from 'axios';
import crypto from 'crypto';
// Define your Digest Authentication credentials
const authOptions = {
username: process.env.ATLAS_PUBLIC_API_KEY,
password: process.env.ATLAS_PRIVATE_API_KEY,
groupId: process.env.ATLAS_CLUSTER_GROUP_ID,
clusterName: process.env.ATLAS_CLUSTER_NAME,
mongoBaseUrl: process.env.MONGO_BASE_URL
};
// Define the path for the MongoDB Atlas API endpoint
let path = `/api/atlas/v1.0/groups/${authOptions.groupId}/clusters/${authOptions.clusterName}`
// Configuration for the PATCH request to MongoDB Atlas API
let config = {
method: 'PATCH',
maxBodyLength: Infinity,
url: `${authOptions.mongoBaseUrl}${path}`,
headers: {
'Content-Type': 'application/json',
'Accept-Encoding': 'bzip, deflate'
},
};
/**
* Function to calculate the digest response for HTTP Digest Authentication.
* @param {string} method - The HTTP method of the request.
* @param {string} uri - The requested URI (Uniform Resource Identifier).
* @param {string} realm - The authentication realm, typically provided by the server.
* @param {string} nonce - A unique value generated by the server to prevent replay attacks.
* @param {string} nc - The nonce count, a client-specified value that increments with each request.
* @param {string} cnonce - A client-generated random value for additional security.
* @param {string} qop - The quality of protection, indicating the algorithm used for the digest.
* @returns {string} - The calculated Digest Authentication response string.
*/
function calculateDigestResponse({ method, uri, realm, nonce, nc, cnonce, qop }) {
const ha1 = crypto.createHash('md5').update(`${authOptions.username}:${realm}:${authOptions.password}`).digest('hex');
const ha2 = crypto.createHash('md5').update(`${method}:${uri}`).digest('hex');
const response = crypto.createHash('md5').update(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`).digest('hex');
return `Digest username="${authOptions.username}", realm="${realm}", nonce="${nonce}", nc="${nc}", cnonce="${cnonce}", uri="${uri}", response="${response}", qop=${qop}`;
}
/**
* Function to parse the components of a Digest Authentication value received from a server.
* @param {string} challenge - The Digest Authentication string from the server.
* @returns {Object} - An object containing parsed key-value pairs extracted from the string.
*/
function parseDigestValue(challenge) {
// Use a regular expression to extract key-value pairs from the challenge string
const matches = challenge.match(/(\w+)="([^"]+)"/g);
// Initialize an empty object to store parsed values
const result = {};
// Iterate through the matches and populate the result object
matches.forEach((match) => {
const [key, value] = match.split('=');
result[key.replace(/"/g, '')] = value.replace(/"/g, '');
});
// Return the parsed key-value pairs as an object
return result;
}
/**
* Asynchronous function to retrieve the status of a MongoDB Atlas cluster using the Digest Authentication mechanism.
* @param {object} data - The data object containing information to be sent in the PATCH request.
* @returns {string} - The status of the MongoDB Atlas cluster.
*/
async function updateCluster(data) {
try {
config.data = data;
// Attempt the initial GET request
await axios(config.url);
} catch (error) {
if (error.response.status === 401) {
// Capture the Digest Authentication challenge in case of an error
let digestValue = error.response.headers['www-authenticate'];
try {
// Parse the Digest challenge to extract realm, nonce, and qop
const { realm, nonce, qop } = parseDigestValue(digestValue);
// Add Authorization header with the calculated digest response to the request configuration
config.headers.Authorization = calculateDigestResponse({
method: config.method,
uri: path,
realm,
nonce,
nc: `00000001`,
cnonce: crypto.randomBytes(8).toString('hex'),
qop
});
// Make the actual request with the updated config, retrieve the cluster status
const finalResponse = await axios.request(config);
let result = finalResponse.data.paused
return result;
}
catch (error) {
error = error?.response?.data?.detail ? error?.response?.data?.detail : error.message
throw new Error(error);
}
} else {
throw new Error(error);
}
}
}
/**
* Asynchronous function to pause or resume a MongoDB Atlas cluster based on its current status.
* @throws Will throw an error if there's an issue with retrieving the cluster status or making the request.
*/
export async function pauseOrResumeCluster() {
let isPaused
let data = JSON.stringify({});
try {
// Retrieve the current status of the MongoDB Atlas cluster
config.method = 'GET'
isPaused = await updateCluster(data);
console.log(`[Cluster current status] [isPaused : ${isPaused}]`);
config.method = 'PATCH'
} catch (error) {
console.log("Error in pauseOrResumeCluster : ", error);
console.log('Error in pauseOrResumeCluster stringified : ', JSON.stringify(error));
}
try {
// Check the current status and take appropriate action
if (isPaused === true) {
// If the cluster is paused, send a request to resume it
data = JSON.stringify({
"paused": false
})
let response = await updateCluster(data);
console.log('[Cluster is resumed succesfully!]',`[isPaused : ${response}]`);
} else if (isPaused === false) {
// If the cluster is running, send a request to pause it
data = JSON.stringify({
"paused": true
})
let response = await updateCluster(data);
console.log('[Cluster is paused succesfully!]',`[isPaused : ${response}]`);
}
} catch (error) {
console.log("Error in pauseOrResumeCluster : ", error);
console.error('Error in pauseOrResumeCluster stringified : ', JSON.stringify(error));
}
}