Failed to write session data in express - node.js

Sometimes the data I'm storing in a session won't be saved.
The next request the data is just gone.
The way I'm storing the data in the session is very straight-forward:
// Store the complete user data in the session
req.session.user = user;
// Redirect to the correct url
res.redirect(redirectUrl);
As you can see, I do a redirect immediately afterwards.
I did have an entry that contained a lot of data.
When I removed that entry, everything started working again.
So now I wonder:
Does this happen because the object is "too big" for a session or
because there is some race condition with the saving of the session?
As far as I can tell, there is no asynchronous function that lets you save the session before doing a redirect...

You can rule out whether the issue is the first function completing by wrapping both of them in async.series: https://github.com/caolan/async#series

Related

Why would an object's prototype be forgotten?

Introduction
So I am building a website with node.js, express, express-session, and sequelize.js. Once a user logs in, an instance of the Sequelize model User is created. In my route for user log-in (/auth/login), I have:
var user = (await User.findAll(
{where: {
username: username
}}))[0];
and I few lines down I assign that user to the session.
req.session.user = user;
And then I can persist any changes by simply calling the save method of req.session.user:
await req.session.user.save();
And indeed, if I add this line next:
console.log(Object.getPrototypeOf(req.session.user));
the output is [object SequelizeInstance:User]. So far so good.
Here is the problem
In another route (/users/myaccount/edit-bio) I am able to access the values of req.session.user. That is, the output of
console.log(req.session.user.username);
is seanletendre, as expected. But now when I call
await req.session.user.save();
all I get is the error message:
UnhandledPromiseRejectionWarning: TypeError: req.session.user.save is not a function
"That is weird," I thought, "isn't this the same object?" To investigate, I add the line:
console.log(Object.getPrototypeOf(req.session.user));
just as I did in the log-in route. And what is the output? It is: [object Object]. So it seems that somehow the prototype of req.session.user gets forgotten. I don't understand how this can be.
Is it possible to re-assign a prototype to a plain object?
Suspect A
Based on the comments to my question, I suspect that the prototype is lost when the session manager serializes req.session. It seems that, unlike I thought before,req.session does not point to the exact same session object for different requests. Each time a request ends, it serializes and stores req.session. Then upon receiving a new request with a cookie designating it as part of the same session, the session object is fetch from the session store.
This is how my session middleware is setup:
var session = require('express-session');
//
// yada, yada, yada
//
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {secure: true}
}));
So what surprises me is that, even though I am using the default store MemoryStore, my sessions are still serialized.
My question now becomes: how can I prevent object serialization upon session store when using MemoryStore?
In express-session the method save() is exposed by the object session into the request object (docs), eg.:
req.session.save(callback)
Your code req.session.user.save() is wrong, the correct way is req.session.save(), diff.:
req.session.user.save();
-----------^^^^^
req.session.save()
The method save() isn't a Promise, you must pass a callback for wait for the result of the save:
req.session.user = user;
req.session.save(function(err) {
if( err ){
// session not saved
} else {
// session saved
}
})
you can transform it into a Promise (and await it), in this way:
const saveSession = (req) => {
return new Promise((resolve, reject) => {
req.session.save(function(err) {
if( err ){
reject(err);
} else {
resolve(true);
}
});
});
};
req.session.user = user;
await saveSession(req);
The method save() is automatically called at the end of the HTTP response if the session data has been altered. Because of this, typically this method does not need to be called.
UPDATE
I call req.session.user.save() because I want to save the Sequelize
Model instance into my database.
express-session use JSON.stringify() for serialize the session into a string and store into a session storage (more info). JSON.stringify() doesn't understand functions and only property are stored. For this reason, your save() function is lost.
Is it possible to re-assign a prototype to a plain object?
Technically you could re-assign the .prototype and .constructor. However, a model instance is quite a complex object. Try logging it to debugging console and you'll see it has lots of sub-objects of its own kind and also references to other more distant objects residing in a "more global" space (nothing like the plain object after deserialization (of JSON basically)). You'd have to re-instanciate them all. That is very hard to do.
My question now becomes: how can I prevent object serialization upon session store when using MemoryStore?
I am not experienced with express-session, but from what i read i think it basically works with parsing and stringifying JSON. So you can't prevent it when using express-session.
What you could do:
Just store the user.id to req.session.user_id. And User.findOne({ where: { id }) upon reloading the session. You then get a "real" sequelize model instance. And that instance you could assign to req.user, so that it's available throughout the request.
If you also hoped to avoid reloading the user's instance upon every request, then you are searching for a "caching" solution, which is different from "sessions". You'd then use a caching system like memcache, redis or others. However, there too, you probably won't get back a "real" instance of the model from the caching server.
If you have only one server where your application is running on, you could have a global.users variable, where you store all user instances via their id. For example "global.users[12345] = user". Like with 1. you'd retrieve it (or when not found initially load it) based on session.user_id
Some more thoughts: In your place, i'd also invest some time in figuring out, if sequelize allows to populate a new User() instance with data from JSON, and tricking sequelize into believing that this is not a new record.
Also, keep in mind, that depending on your application, the user's data in the database can change, while in your session or global.users you miss these changes.

Avoid req.flash delete data on middleware

I'm using connect-flash with express to handle flash messages, but that messages are deleted when i read that properties, for example:
Defining flash message:
req.flash("myMessage", "Hey!");
Reading message:
console.log(req.flash("myMessage")); // Hey!
console.log(req.flash("myMessage")); // undefined
Data is erased on first read, and this is a problem, because i need to read the data of req.flash in a middleware to filter the http request and after in the controller (Which is in another file) i need to read the same req.flash data but i can't because in the read of the middleware was erased.
What can i do in this case?
The messages are stored on req.session.flash internally, see the source code.
This means you can get the message by indexing directly into req.session.flash["myMessage"] without it getting cleared.
This is technically an implementation detail of req.flash() you shouldn't rely on, but I think if you pin the version of connect-flash in your requirements it's safe enough.
Calling the req.flash again obviously the first call actually delete the flash data. So that's why when you call again the req.flash the data is now undefined or empty array.
1st console.log output: [ 'Hey!' ]
2nd console.log output: []
Does it make sense?
Store the message in the request object like:
req.myMessage = req.flash("myMessage");
And that will be available in all middlewares you want for that specific request.
Btw, make sure you are using connect-flash because you are redirecting the user to another route and that starts a new request and you want to save data between requests (and that's why you want to use a session for that). That being said, removing the message from the session is encouraged instead of just trying to access it.
If you are not redirecting the user, rethink if connect-flash is necessary.

Immediately update cookies in KOA

When using ctx.cookies.set(), KOA adds a 'set-cookie' header to the response. However, ctx.cookies.get() retains the cookie (if any) from the original request. This seems counter-intuitive if I'm overwriting it. Is there not a way to have the getter reflect the new value immediately, in the same request?
h = uuidv4();
console.log('set new cookie',h);
ctx.cookies.set('uuid',h,{httpOnly:false,overwrite:true});
//This outputs undefined or the value that came with the request,
//not the newly assigned value:
console.log('cookie is',ctx.cookies.get('uuid'));
I'm fairly new to node.js. What I'm looking for is something like PHP's $_SESSION[], in which values are updated and available immediately as well as being written to the response cookie. I understand I could pile the new uuid into ctx.state, but it seems cleaner to just be able to access ctx.cookies.get() anywhere further down the middleware chain and have access to what I just set it to.
Having a set() that doesn't change the result of its get() seems like code smell to me. Am I missing something?

sails.js Use session param in model

This is an extension of this question.
In my models, every one requires a companyId to be set on creation and every one requires models to be filtered by the same session held companyid.
With sails.js, I have read and understand that session is not available in the model unless I inject it using the controller, however this would require me to code all my controller/actions with something very, very repetitive. Unfortunate.
I like sails.js and want to make the switch, but can anyone describe to me a better way? I'm hoping I have just missed something.
So, if I understand you correctly, you want to avoid lots of code like this in your controllers:
SomeModel.create({companyId: req.session.companyId, ...})
SomeModel.find({companyId: req.session.companyId, ...})
Fair enough. Maybe you're concerned that companyId will be renamed in the future, or need to be further processed. The simplest solution if you're using custom controller actions would be to make class methods for your models that accept the request as an argument:
SomeModel.doCreate(req, ...);
SomeModel.doFind(req, ...);
On the other hand, if you're on v0.10.x and you can use blueprints for some CRUD actions, you will benefit from the ability to override the blueprints with your own code, so that all of your creates and finds automatically use the companyId from the session.
If you're coming from a non-Node background, this might all induce some head-scratching. "Why can't you just make the session available everywhere?" you might ask. "LIKE THEY DO IN PHP!"
The reason is that PHP is stateless--every request that comes in gets essentially a fresh copy of the app, with nothing in memory being shared between requests. This means that any global variables will be valid for the life of a single request only. That wonderful $_SESSION hash is yours and yours alone, and once the request is processed, it disappears.
Contrast this with Node apps, which essentially run in a single process. Any global variables you set would be shared between every request that comes in, and since requests are handled asynchronously, there's no guarantee that one request will finish before another starts. So a scenario like this could easily occur:
Request A comes in.
Sails acquires the session for Request A and stores it in the global $_SESSION object.
Request A calls SomeModel.find(), which calls out to a database asynchronously
While the database does its magic, Request A surrenders its control of the Node thread
Request B comes in.
Sails acquires the session for Request B and stores it in the global $_SESSION object.
Request B surrenders its control of the thread to do some other asynchronous call.
Request A comes back with the result of its database call, and reads something from the $_SESSION object.
You can see the issue here--Request A now has the wrong session data. This is the reason why the session object lives inside the request object, and why it needs to be passed around to any code that wants to use it. Trying too hard to circumvent this will inevitably lead to trouble.
Best option I can think of is to take advantage of JS, and make some globally accessible functions.
But its gonna have a code smell :(
I prefer to make a policy that add the companyId inside the body.param like this:
// Needs to be Logged
module.exports = function(req, res, next) {
sails.log.verbose('[Policy.insertCompanyId() called] ' + __filename);
if (req.session) {
req.body.user = req.session.companyId;
//or something like AuthService.getCompanyId(req.session);
return next();
}
var err = 'Missing companyId';
//log ...
return res.redirect(307, '/');
};

Node.js res.send is not working

In my index.js, I have an exports function that is supposed to send data back to the client via ajax on pressing a submit button. However, when the user presses submit, the data seems to get sent over before it the data gets modified. When pressing submit one more time, it sends the data that was previously modified as if clicking the submit button only sends the 'previously' set data. This is my code:
var tabledata = getRecordFromDatabase(key);
if(tabledata.length === 0)
tabledata = 'There is no matched record in the database';
res.contentType('text/html');
res.send({'matched':tabledata});
So to illustrate the error: I click submit after filling out a form and receive back the message "There is no matched record in the database". I hit submit a second time without changing anything in the form I just filled. This time record data is actually sent to me. Why could this be?
If whatever you're doing in getRecordFromDatabase is asynchronous and non-blocking, then node.js is behaving as it should. Node.js is non-blocking - it doesn't stop and wait for processes to complete (unless those processes are intentionally written to block, which is usually avoided in node.js). This is beneficial, because it keeps the server free to accept new requests and process many requests at once.
If your database call is asynchronous, you're not waiting for it to return before you res.send(). That's why your first submit returns back empty. Most likely, by the time you hit submit a second time, your DB call has finally returned, and that's why you get a result.
It's hard to give you a code-based answer to your problem, because you abstracted away what is happening in your DB call method. But typically, an asynchronous call would go something like:
getRecordFromDatabase(key, function(err, data){
if(data.length === 0)
data = 'There is no matched record in the database';
res.contentType('text/html');
res.send({'matched':data});
});
This way, you are passing a function to execute as a callback to your asynchronous method - when the async call completes, it executes the callback, which then executes the res.send() with the appropriate data.

Resources