Node - Receiving ECONNRESET error when doing an http request with axios - node.js

I have a function in Node that needs to fetch all of the Users from Azure AD (I'm utilizing Microsoft Graph and Axios).
This function queries Microsoft Graph, the response includes an array of users. The max number of objects in this array is capped at 999. If there are more users, this response will also include an #odata.nextLink. My plan was to query, and if there is an #odata.nextLink in the response, query again until there is no #odata.nextLink. Not entirely sure if there is another way to do this better.
async function fetchUsers(tokenEndpoint, client_id, client_secret, query) {
try {
let allUsers = [];
let bearer = `Bearer ${await fetchToken(
tokenEndpoint,
client_id,
client_secret
)}`;
let res = await fetchData(bearer, `${msAPI}${query}`);
if (res.status === 200) {
allUsers.push(...res.data.value);
console.log(allUsers.length);
let nextURL = res.data["#odata.nextLink"];
while (nextURL != undefined) {
bearer = `Bearer ${await fetchToken(
tokenEndpoint,
client_id,
client_secret
)}`;
res = await fetchData(bearer, nextURL);
allUsers.push(...res.data.value);
nextURL = res.data["#odata.nextLink"];
console.log(allUsers.length);
}
}
return allUsers;
} catch (err) {
handleError(err);
}
}
async function fetchToken(tokenEndpoint, client_id, client_secret) {
try {
const res = await axios.post(
tokenEndpoint,
qs.stringify({
client_id,
client_secret,
grant_type: "client_credentials",
scope
})
);
return res.data.access_token;
} catch (err) {
handleError(err);
}
}
async function fetchData(bearer, url) {
try {
const res = await axios.get(url, {
headers: {
Authorization: bearer
}
});
return res;
} catch (err) {
handleError(err);
}
}
I have this statement in another async function
const allUsers = await fetchUsers(tokenEndpoint,clientId,clientSecret,query);
Sometimes this runs perfectly and I get all my Azure AD users and sometimes I receive an ECONNRESET error in the middle of the function being run and it won't finish. This error seemed to happen a few times on one network, then I tested on a different network and didn't get the error once. Swapped back to the original network and got the errors again. Not sure if this is a network issue or the way I am doing the Axios requests in my loop.
Regardless, how would I go about resolving this ECONNRESET issue?

An ECONNRESET occurs when a TCP abruptly closed. This could be caused by a failure in your local network, the remote service, or anywhere in between the two.
The most likely cause here is requesting $top=999. That is an excessively large (and therefore long) page size. You should be setting this to a smaller value or, ideally, leaving it to the default. This ensures the page sizes are reasonable, requests complete quickly, and helps avoid potential getting throttled by AAD.
Your "follow each #odata.nextLink plan is the correct pattern to use. This allows you to pull a page of data and process it prior to making subsequent requests for additional pages. In between requests you'll want to store the current #odata.nextLink so if anything goes wrong, you can pick up where you left off.

Related

JWT Silent Refreshing based on Redis - MERN Stack

I've been struggling so long with silent refreshing to make it the most transparent to client.
I've implemented so many strategies so far and still I have no clue how solve it fully.
When I perform silent refreshing I put my recent generated refresh token in redis db for further access token generation so as to it carries out refreshing for both everytime when there is a need to refresh.
Due to React Strict Mode in dev mode, request is being made twice so first time it can be pulled out from db and showed normally but consecutive request acts like value in db doesn't exist and it spits null value.
My middleware is written in express js, I put my code beneath:
const authRefresh = async (req, res) => {
try {
const refreshToken = await req.cookies['refresh-token'];
const accessToken = await req.cookies['access-token'];
if (!refreshToken) {
return res.status(401).json({ status: 'error', message: "Refresh token not found" })
}
if (!accessToken || accessToken.includes('mockup'))
jwt.verify(refreshToken, process.env.JWT_REFRESH, async (err, userProperties) => {
if (err) {
return res.status(401).json(err)
}
const redisValue = await redisClient.get(refreshToken);
if (redisValue) {
const tokens = await generateToken(userProperties)
//setting tokens in httpOnly cookies
res.cookie('access-token', tokens.accessToken,
cookiesTime.access);
res.cookie('refresh-token', tokens.refreshToken,
cookiesTime.refresh);
await redisClient.del(redisValue);
await redisClient.set(tokens.refreshToken, tokens.refreshToken);
await redisClient.expire(tokens.refreshToken, time_refresh);
return res.status(200).json('Tokens set again')
}
else {
return res.status(401).json('No refresh token in db');
}
})
else return res.status(202).json('No need to refresh an access token')
} catch (err) {
return res.status(401).json({ status: 'error', message: err })
}
}
PS. I want to mention I've tried rearrange redis methods from asynchronous to synchronous and backwards. It seems the same for my action.

Puppteer post request

I'm trying to use puppeteer in order to fill the cart in the online store.
(is that legal, right?)
Anyway, the action should post the payload to the store server and I'm doing so in this way.
This code run after login to the system as user.
await page.setRequestInterception(true);
page.on("request", async (request) => {
if (request.url() === "https://www.store.com/server/carts/?userID=xyz&appId=x&loyalty=z") {
const res = await request.continue({
method: "POST",
postData: JSON.stringify(cartContent), // cartcontent is arr of object
headers: { ...request.headers(), ...moreHeaders }, // see note bellow the code
});
console.log(request.headers());
} else {
request.abort();
}
});
await page.goto("https://www.store.com/server/carts/?userID=xyz&appId=x&loyalty=z");
await browser.close();
I don't know why, but nothing actually happened. I believe is up to the request headers.
so I added the request.headers(), but when I log those headers they missing some other headers and contnent not same. so I hard-coded them from the browser when I'm logged in as a user. but still i fill i have lake of knowlege that i will glad to learn.
Headers from puppter (request.headers())
upgrade-insecure-request,user-agent,sec-ch-ua,sec-ch-ua-mobile,sec-ch-ua-platform,accept,cookie
Headers from browswer contains :
:authority,:method,path,scheme,accept,accept-encoding,accept-language ,authorization, content-length,content-type,cookie,origin,referer,
sec-ch-ua,sec-ch-ua,sec-ch-ua-platform,sec-fetch-dest,sec-fetch-mode,
sec-fetch-site,sec-fetch-site,user-agent,x-http-method-override

register webhooks on nodejs when order created

I have a shopify store mystore and I have an nodejs app myapp. I need to do is when something happens on mystore a webhook will be created/registered in my nodejs app. I have tried https://www.npmjs.com/package/#shopify/koa-shopify-webhooks this package but it is not working for me and I don't think that it is the same thing that I want. I just want that when let suppose order is created in store a webhook is registered.
if you just have to register a webhook you can use this code.
You just have to change the webhook topic and the endpoint.
This is for orders/create webhook registration
add shopify-api-node and request-promise packages and require them
const ShopifyAPIClient = require("shopify-api-node");
const request = require("request-promise");
then
const createOrderWebhook = await registerWebhook(yourShopDomain, yourShopAccessToken, {
topic: "orders/create",
address: "Your node app end point" //www.example.com/webhooks/createOrder,
format: "json",
});
add your registerWebhook function
const registerWebhook = async function (shopDomain, accessToken, webhook) {
const shopify = new ShopifyAPIClient({
shopName: shopDomain,
accessToken: accessToken,
});
const isCreated = await checkWebhookStatus(shopDomain, accessToken, webhook);
if (!isCreated) {
shopify.webhook.create(webhook).then(
(response) => console.log(`webhook '${webhook.topic}' created`),
(err) =>
console.log(
`Error creating webhook '${webhook.topic}'. ${JSON.stringify(
err.response.body
)}`
)
);
}
};
for checking the webhook already not created at Shopify you can use following code
const checkWebhookStatus = async function (shopDomain, accessToken, webhook) {
try {
const shopifyWebhookUrl =
"https://" + shopDomain + "/admin/api/2020-07/webhooks.json";
const webhookListData = {
method: "GET",
url: shopifyWebhookUrl,
json: true,
headers: {
"X-Shopify-Access-Token": accessToken,
"content-type": "application/json",
},
};
let response = await request.get(webhookListData);
if (response) {
let webhookTopics = response.webhooks.map((webhook) => {
return webhook.topic;
});
return webhookTopics.includes(webhook.topic);
} else {
return false;
}
} catch (error) {
console.log("This is the error", error);
return false;
}
};
Happy coding :)
You can not create/register a new webhook when the order created.
Webhooks are a tool for retrieving and storing data from a certain event. They allow you to register an https:// URL where the event data can be stored in JSON or XML formats. Webhooks are commonly used for:
Placing an order
Changing a product's price
Notifying your IM client or your pager when you are offline
Collecting data for data-warehousing
Integrating your accounting software
Filtering the order items and informing various shippers about the order
Removing customer data from your database when they uninstall your app

How do I clear a session or logout using the LinkedIn REST API

I'm currently writing a program in which multiple accounts need to be authenticated through the LinkedIn REST API. This needs to be done in fairly quick succession as it's quite common to have to re-connect multiple accounts at once due to LinkedIn's fairly short-lived tokens.
The issue is that after authenticating a single account I then have to wait ~10 minutes for the session to expire before authenticating a new user. When the API is called (liLogin) it will automatically log the user in as part of the existing session.
I believe this is an intentional feature of the API as you can see mentioned in Step 5 of The Authorization Code Flow Documentation.
Other things to note. I've tried adding a random string as a query on the callback URL which was mentioned in another thread. I've also noticed the Android SDK has a logout function but can't seem to locate anything for this within the REST API.
I've pasted the part of my code that handles this below in-case this helps in anyway.
// Login
const liLogin = async (req, res) => {
const URL = `https://www.linkedin.com/oauth/v2/authorization?client_id=${config.li.id}&redirect_uri=${config.li.redirectUri}&response_type=code&state=PostSchedulerCapture321&scope=r_liteprofile%20r_emailaddress%20w_member_social`;
res.redirect(URL);
};
// Exchange temp token for access_token
const liGetUser = (req, res) => {
let token = req.query.code;
axios.get(`https://www.linkedin.com/oauth/v2/accessToken`, {
params: {
"grant_type": "authorization_code",
"code": token,
"redirect_uri": config.li.redirectUri,
"client_id": config.li.id,
"client_secret": config.li.secret
}
})
.then(result => {
return result.data.access_token;
})
.then(access_token => {
axios.get('https://api.linkedin.com/v2/me', {
'headers': {
'Authorization': `Bearer ${access_token}`
}
})
.then(result => {
res.send(result.data)
})
.catch(err => {
console.log(err);
})
})
.catch(err => {
err = {...err.config.headers, ...err.response.data};
console.log(err);
})
}
In-case anyone comes across this you can simply redirect the browser to the logout URL (https://linkedin.com/m/logout) and then use setTimeout to close the window after a few seconds.
Not an ideal solution but it works.

How can I change a VM/Instance label with GCP Cloud Function using node.js?

I am testing a Cloud Function in GCP and I want to modify labels of my compute instance with Cloud Function i.e. change label "status=active" to "status=tobedeleted".
Is there a way to do it with Cloud Function and node.js ?
Looks as if the method compute.instances.setLabels requires additional libraries ?
I already created Cloud Function to stop/start instances.
Here is the error :
resource: {…}
severity: "ERROR"
textPayload: "{ Error: Login Required
at Gaxios.request (/srv/node_modules/googleapis-common/node_modules/gaxios/build/src/gaxios.js:70:23)
at
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
response:
{ config:
{ url: 'https://www.googleapis.com/compute/v1/projects/wpress-v1/zones/us-central1-a/instances/instance-1/setLabels?labels%5Bis-scheduled%5D=manual',
method: 'POST',
paramsSerializer: [Function],
headers: [Object],
params: [Object],
validateStatus: [Function],
retry: true,
responseType: 'json',
retryConfig: [Object] },
data: { error: [Object] },
Then here is my code :
const Compute = require('#google-cloud/compute');
/*const compute = new Compute();*/
const {google} = require('googleapis');
/*const google = require('#google-cloud/googleapis');*/
var compute = google.compute('v1');
exports.setInstanceScheduleMode = (event, context, callback) => {
try {
const payload = _validatePayload(
JSON.parse(Buffer.from(event.data, 'base64').toString())
);
var request = {
project: 'wpress-v1',
zone: 'us-central1-a',
instance: 'instance-1',
labels: {
"is-scheduled": "manual"
},
auth: google.authClient,
};
compute.instances.setLabels(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
} catch (err) {
console.log(err);
callback(err);
}
};
// [END functions_start_instance_pubsub]
function _validatePayload(payload) {
if (!payload.zone) {
throw new Error(`Attribute 'zone' missing from payload`);
} else if (!payload.label) {
throw new Error(`Attribute 'label' missing from payload`);
}
else if (!payload.instance) {
throw new Error(`Attribute 'instance' missing from payload`);
}
return payload;
}
function authorize(callback) {
google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
}).then(client => {
callback(client);
}).catch(err => {
console.error('authentication failed: ', err);
});
}
There's a good number of things going on in this code. This isn't a straightforward operation, and I do wish there were a few more examples in the documentation of how to do this.
First, it appears that the #google-cloud/compute idiomatic library doesn't support a setLabels function on its VMs object, so we're forced to use the node REST library, which isn't quite as easy to use. The code you have written seems to mix the two in a somewhat confusing way, but is mostly already using the REST API so we can start from there. For reference, the setLabels REST API documentation.
Second, the authentication error you are getting is because you haven't properly intilized the authClient for the REST API, in particular by granting it the correct scope. (Notably, the authorize() method is never called, unlike in the sample code). This needs to be called to at least request the https://www.googleapis.com/auth/compute scope, though the cloud-platform scope will also work, as it is more privileged. This is what is leading to your immediate authentication error.
It is also possible that you are running the cloud function as an IAM account without the necessary roles, but both the default compute engine and default app engine accounts should be able to do this, so it appears to be that the scopes aren't requested.
Finally, even if this was working, you would find that the setLabels method requires a fingerprint of the current label values, or it would return a CONDITION_FAILURE -- essentially, when you call setLabels you are fully replacing the labels on the instance, so the API wants to make sure two callers aren't competing at once.
All together, that leads us to this (for simplicity, I used an HTTP function, but of course you can use your existing trigger just as well):
const { google } = require('googleapis');
const computeClient = google.compute('v1');
exports.labelInstance = async (req, res) => {
// First, get the auth scope we need. Thankfully cloud functions runs with
// application default credentials, so we don't need to do anything with keys, etc
// as long as the service account we are configured to run as has the right permissions.
//
// We only need the compute scope, we don't need all of cloud-platform, so limit ourselves to that.
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/compute']
});
const authClient = await auth.getClient();
// Build our request
var baseRequest = {
project: 'YOUR-PROJECT-NAME',
zone: 'us-central1-a',
instance: 'instance-1',
auth: authClient
};
// We need to get the existing labels and fingerprint first.
return computeClient.instances.get(baseRequest).then(result => {
// We need all the fields from baseRequest again, and we want to keep the old labels.
// I'm sort of cheating here, since it isn't a deep copy, but it works within the
// scope of this function.
setRequest = baseRequest;
// As setLabels is a POST request, we need to put the parameters in the requestBody.
setRequest.requestBody = {
labels: result.data.labels || {},
labelFingerprint: result.data.labelFingerprint // Needed to avoid CONDITION_FAILURE
};
// And add our new label...
setRequest.requestBody.labels['my-new-label'] = 'my-new-value';
return computeClient.instances.setLabels(setRequest);
}).then(result => {
console.log('set done');
console.log(result);
return res.send('ok');
}).catch(error => {
console.error('Error!');
console.error(error);
return res.send('error');
});
};
In your original question you wanted to change a label. Obviously you can adjust the code above to remove any labels out of the set that is retrieved with the fingerprint that you like, you don't have to copy them all.
Also be aware that the above code doesn't actually wait for the operation to complete (as operations are asynchronous -- the result that is returned will likely be in the RUNNING state), you would need to further use the REST API to check on the status of the operation. I haven't done that as it is somewhat outside the scope of this question, but you can read about it here.
Google Cloud Platform documentation provides a detailed overview of instances.setLabels method, which is a part of Google's Node.js client library.
See the Node.js code sample mentioned in GCP documentation below:
// BEFORE RUNNING:
// ---------------
// 1. If not already done, enable the Compute Engine API
// and check the quota for your project at
// https://console.developers.google.com/apis/api/compute
// 2. This sample uses Application Default Credentials for authentication.
// If not already done, install the gcloud CLI from
// https://cloud.google.com/sdk and run
// `gcloud beta auth application-default login`.
// For more information, see
// https://developers.google.com/identity/protocols/application-default-credentials
// 3. Install the Node.js client library by running
// `npm install googleapis --save`
const {google} = require('googleapis');
var compute = google.compute('v1');
authorize(function(authClient) {
var request = {
// Project ID for this request.
project: 'my-project', // TODO: Update placeholder value.
// The name of the zone for this request.
zone: 'my-zone', // TODO: Update placeholder value.
// Name of the instance scoping this request.
instance: 'my-instance', // TODO: Update placeholder value.
resource: {
// TODO: Add desired properties to the request body.
},
auth: authClient,
};
compute.instances.setLabels(request, function(err, response) {
if (err) {
console.error(err);
return;
}
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
});
});
function authorize(callback) {
google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
}).then(client => {
callback(client);
}).catch(err => {
console.error('authentication failed: ', err);
});
}
Remember to send the request body as a parameter when writing your code.
Consider the following when using this method:
You will need to specify the current labels your instance has under labelFingerprint.
Your instance's labels will be overwritten, so make sure to include in request body any labels you'd want to keep.

Resources