I'm using Firebase Cloud Functions, and the cookie module, and one function sets the "__session" cookie (the only cookie available in Cloud Functions) then executes a "GET" request to another function. In the second function when I try to get the cookie using cookie.parse(req.headers.cookie).__session it says it's undefined. I can see the cookie in the browser however, so I have no idea why I cannot get it in my app. I noticed there are several other SO questions similar to this problem that are also unanswered.
Here is the relevant code from the first function:
const nonce = require('nonce')();
const state = nonce();
res.cookie('__session', state);
request.get('https://myappurl.firebaseapp.com/myapp/path?state=' + state, { json: true })
.catch((error) => {
console.log(error.message);
});
res.redirect('someurl.com');
From the second function:
const cookie = require('cookie');
app.get('/myapp/path', (req, res) => {
console.log(req.headers.cookie); // This is undefined.
const stateCookie = cookie.parse(req.headers.cookie).__session
console.log('stateCookie: ' + stateCookie); // This is undefined.
}
UPDATE
After adding my code, I realized that I'm setting the cookie on res (Express response) but making the get request with request (request-promise module). I'm thinking this may be my issue. I'm going to figure out how to set a cookie to the request and see if that solves it.
I solved this, mostly. Turns out setting the cookie on the Express res was part of the problem. I should be setting it on the request object (request-promise). I tried a couple of ways to set the cookie on the request, including request.cookie. But the only way that worked was this:
var options = {
headers: {
Cookie: '__session=mytestheadercookie'
}
};
request.get('https://myapp.com', options)
.catch((error) => {
console.log(error.message);
});
Related
I know what is wrong with my code and I have looked into the best way of solving it, however with my lack of experience, I am having a hard time finding a good answer.
I need my first route(/data) to be fully completed before the second(/logo) express route sends the data. In short, I just need the variable symbolUrl to be completed before it goes into the second fetch call. Here is the code down below to explain
app.use(express.static('public'));
const url =
'https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest';
const qString =
'?CMC_PRO_API_KEY=' + process.env.apiKey + '&start=1&limit=10&convert=USD';
let symbol = [];
app.get('/data', async (req, res) => {
const fetch_res = await fetch(url + qString);
const coinData = await fetch_res.json();
for (let i = 0; i < 9; i++) {
symbol.push(coinData.data[i]['symbol']);
};
res.json(coinData);
});
app.get('/logo', async (req, res) => {
const symbolUrl = symbol.join(',');
const url2 = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/info';
const qString2 = `?CMC_PRO_API_KEY=${apiKey}%symbol=${symbolUrl}`;
const fetch_res2 = await fetch(url2 + qString2);
const coinLogo = await fetch_res2.json();
res.json(coinLogo);
});
The issue I am trying to solve with this project is that I want to send the data(/data) to be sent to the front end first because this API call will load the majority of the page. Then my second call will load images and other larger files afterward. HOWEVER, the API I am working with to get the logos(images) of the specific crypto coins I want, I need a different endpoint as well as use %symbol=${symbolUrl} in the API call to get the correct tokens I want to call.
client code:
fetch('http://localhost:2000/data')
.then(async (response) => {
return response.json();
})
.then(async (data) => {
const parsedData = data['data'];
// console.log(data['data'][0]['name'])
await parsedData.forEach((element) => {
// this just has about 20 lines of code generating the the look of the page. It works as intended
});
fetch('http://localhost:2000/logo')
.then(async (response) => {
return response.json();
})
.then(async (logo) => {
console.log(logo)});
***I have tried putting this in an async function and awaiting the first fetch call
All I need to be done is for app.get(/data) to be fully complete before doing my second app.get. I have done testing and I know that is the issue. I apologize if it is something easy, but I couldn't find anything on making an app.get synchronous and I have tried putting both in a async function, however that did not work.
You cannot send responses in fragments like you're trying to do, it would throw an error saying Can't set headers after they are sent to client
The proper method to implement what you are trying to do is to define the first layer as middleware, and then allow the second layer to return the response. Here layer basically means a function handler.
In order to control when the execution passes to the next layer / next function handler, express has a third parameter (request, response, next). You're only using request and response, researching about next will solve your concern.
Express next function, what is it really for?
First handler
app.get('something_unique', async (req, res, next) => {
// do whatever you want to do first
// save data into res.locals
res.locals.foo = {...}
next()
})
Second Handler
app.get('something_unique', (req, res) => {
const data = res.locals.foo;
// whatever you want
return res.json({ anything })
})
More:
Express next function, what is it really for?
Error: Can't set headers after they are sent to the client
Passing variables to the next middleware using next() in Express.js
I'm not sure what client code you're really running as it sounds like you've bee trying several things, but this should work to sequence the /data request and the /logo request so that the /logo request is not run until the response from the /data request has been received.:
async function run() {
const r1 = await fetch('http://localhost:2000/data');
const data = await r1.json();
const parsedData = data.data;
parsedData.forEach((element) => {
// this just has about 20 lines of code generating
// the the look of the page. It works as intended
});
const r2 = await fetch('http://localhost:2000/logo');
const logo = await r2.json();
return logo;
}
run().then(logo => {
console.log(logo);
}).catch(err => {
// handle errors here
console.log(err);
});
If there is any asynchronous code inside the .forEach(), then we will have to see that also to properly sequence that.
As I've said in my comments, stuffing the data from the first request into a server-side variable is probably the wrong design on the server because two separate clients both issuing /data requests will conflict with one another, creating race conditions. But, you haven't explained what this data is really for or why you're stuffing it into a variable on the server for us to suggest an alternate design.
I fetch data at server side and push the result to global variable and then send that global variable to client with app.post method using Express.js. My problem is that client fetches the global variable too soon without the data received from the API first. How can I evaluate the response so that client would wait the global variable to reveive data first before displaying anything.
Server side, code looks something like this:
let sharpe = ''
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Sharpe is the global variable storing the response from multiple API calls and is the one that should be sent back to client. Client side code is this:
const sendData = async (event) => {
event.preventDefault();
var stock1 = document.getElementById('weight1').value
var stock2 = document.getElementById('weight2').value
const data = {stock1, stock2};
const options = {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json' }
}
fetch('/api', options).then(res => res.json()).then(res => {
console.log(res.stocks);
})
}
As a result SendData() function fetches the sharpe variable that is empty at the moment. How can I adjust client side code or server side code that the client waits for a correct response? Thanks.
One solution would be to store the API results to database and client would fetch ready made datastream but is there more straightforward solution?
To wait for your API Server to set the sharpe Parameter, it needs to be awaited, which you already did. It depends on the setup function (for example setup()) which fills the sharpe parameter. It has to return a promise, which is resolved once sharpe is filled with the data.
let sharpe = ''
async setup() {
return new Promise((resolve, reject) => {
sharpe = 'test';
// set value for sharpe
resolve()
})
}
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Eventually it starded working when I pushed all the API calls in the app.post middleware at the server side using promise chaining. The initial problem remains a mystery.
I am looking to use supertest to test API requests and responses. Following is what I have tried so far.
route.test.js
const testUtils = require('./setupTestUtils');
let authenticateUser = request.agent(app);
before(function(done){
testUtils.login(authenticateUser, userCredentials).then((res) => {
expect(res.statusCode).to.equal(200);
done();
}, (err) => {
console.log(err);
done(err);
});
});
setupTestUtils.js
function login (rest, testUserLogin) {
let defer = Q.defer();
rest.post('/login')
.send(testUserLogin)
.expect(200)
.end(function () {
rest.get('/loggedin')
.expect((res) => {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
defer.reject(err);
} else {
defer.resolve(res);
}
})
.end();
});
return defer.promise;
}
In my app.js, I use passport to authenticate. After authentication, I use the session.regenerate function to regenerate the session ID to avoid session fixation.
The initial post request to login passes without any failure. However, the subsequent GET request 'loggedIn' fails. This function internally uses the req.isAuthenticated() function from passport. This always returns false.
On investigation, I found that the session ID between the regenerated session and the request object (for req.isAuthenticated()) is different.
From my search, I understand that the cookies should be maintained automatically by the use of 'agent' from supertest. However that doesnt seem to be the case for me. I have also tried maintaining the cookies from the initial response. That doesnt seem to work for me either. " res.headers['set-cookie'] " comes in as undefined (not sure why that is happening either).
Can someone please help me understand what I am missing here.?
Am using versions - Supertest #v6.0.1 and passport #v0.4.1
I found the solution to my issue in an old github issue raised on supertest's page. Linking it here for reference.
Essentially, the supertest runs express in insecure port and I had configured my session otherwise. Ideally, we would have to check the environment before setting this variable to false - as represented here.
Hope this saves someone the time I spent!
From inside my expressJS application I have to verify that a cookie token is valid with a back-end server. So the relevant code involved in this is as follows:
app.get('*', (req, res, next) => {
console.log('GET: ' + req.path);
// ...
const payload = JSON.stringify({ authnToken: token });
const opts = { ... authServerOptions };
opts.headers['Content-Length'] = payload.length;
// build request
const restReq = https.request(authServerOptions, result => {
console.log('back-end response' + result.statusCode);
result.on('data', data => {
next(); // token is good now proceed.
});
result.on('error', error => {
res.redirect('somewhere'); // token is bad or timeout
});
});
restReq.write(token);
restReq.end();
}
So the main get function sets the REST request in motion and then just returns without calling next() or anything.
Questions:
Is this the right code for doing this? What happens if the callbacks are never called?
Is the application blocked from processing other requests until the back-end server returns or times out?
If so is there some way of freeing up the thread to process more requests?
Thanks in advance for any help. I haven't found many examples for this code pattern so if there is one a link would be appreciated.
Yes, I think the general idea of your implementation is correct.
I would also suggest, as done in the comments, to use a client such as axios to handle the request in a less verbose and more comprehensive manner, which would leave your code looking something like this:
const axios = require('axios');
app.get('*', (req, res, next) => {
const payload = JSON.stringify({ authnToken: token });
const opts = { ... authServerOptions };
opts.headers['Content-Length'] = payload.length;
axios.post(url, payload, opts)
.then(response => next())
.catch(error => {
console.error(error);
res.redirect('somewhere');
});
});
A bit more to the point, but functionally almost equivalent to your implementation. The one thing you are missing is the onerror callback for your request object, which currently may fail and never return a response as you correctly suspected. You should add:
restReq.on('error', error => {
console.error(error);
res.redirect('somewhere');
});
On the same vein, it would probably be more fitting to call next on result end, instead of doing so while reading response data:
result.on('end', () => {
next();
});
Then you'd be covered to guarantee that a callback would be invoked.
Neither implementation blocks the processing of future requests, as the call to the token validation service is done asynchronously in both cases.
In my website, everything server-side is stored in sessions of express-session.
But I can't understand why, when I make an HTTP request with request module, the req.session parameter isn't within the request.
I mean, follow the comments :
app.get('/prefix', async function(req, res) {
console.log(req.session.login);
// There ^ the req.session.login is true, and so it works
if (req.session.login == false || req.session.login == null) return;
var options = {
url: 'https://bot-dreamsub.glitch.me/getPermission',
json: true,
jar: true,
withCredentials: true
}
request(options, async function(err, res, json) {
if (err) {
throw err;
}
console.log(json);
if (json == true) {
res.sendFile(__dirname + '/prefix/prefix.html');
} else {
return;
}
});
});
app.get('/getPermission', function(req, res) {
console.log(req.session.login);
// There ^ the req.session.login is undefined, and so it sends null to the user
try {
if (req.session.login == false || req.session.login == undefined) {
res.send(null);
} else {
// some other code
}
} catch(err) {
console.log(err);
};
});
I don't know why request doesn't send sessions within the HTTP request even with
withCredentials: true
What can I do to accomplish it?
An express-session works by setting a cookie in the client's browser that made the original request. Future requests with that same cookie will offer access to that same session. When you do request() yourself from your server, you aren't presenting the same cookie that came in with the original /prefix request so you won't have access to the same session.
Since it appears you are just trying to use request() to call your own server, I'd suggest you just use a function call and pass the original req.session to that function call so that you will have it available.
You then use normal code factoring to factor out some common code between your /getPermissions route and what you want to use in your /prefix route so that they can both use and share a common function that you pass the current req and res to. Then you don't need to solve this cookie issue because you'll already have the right req object and thus the correct req.session in this factored common function.
Alternatively, you could build the right cookie and send that with your request() so that it will appear to be coming from the original browser that has the cookie (and thus session) that you want, but that's kind of the long way to do things when you already have the req.session you want and you could just pass it in a function call rather than start all over and try to simulate a cookie that will get you to the right session.
I don't know why request doesn't send sessions within the HTTP request even with
First off, session aren't sent with a request. Cookies are. Your server then uses the cookie to find the right session object.
Your call to request() does not have the right cookie in the cookie jar you use so when that requests gets to your server, it isn't able to find the right session object. So, when the request is received by your server, it appears to be coming from a different client that does not yet have a session so a new cookie and a new session are probably created for it.
FYI, if also looks like you may be confusing two definitions of res in your request() call. There's a res defined as an argument in this app.get('/prefix', async function(req, res) { and then you have a separate res in request(options, async function(err, res, json) { that will override the previous one in that scope. It appears to me when you do res.sendFile(__dirname + '/prefix/prefix.html');, you are probably using the wrong res. Probably the best way to solve this is to not use request() at all as suggested above (using a function call to your own server). But, if you were going to still use request(), then you need to name the two res arguments differently so you can still access them both and can use the correct one for your situation.