SetTimeout is not working in Mongoose schema post middleware - node.js

I am trying to update forgetpassword_token to null in mongodb document after 24 hours of generating forgetpassword_token. So I am using Mongoose schema middleware and setTimeout, but setTimeout is not working.
I have tried to implement async await which is also not working as per my result.
CompanySchema.post('updateOne',true, function(doc,next){
next();
setTimeout(this.update({},{ $set: { forgetpassword_token: null } }).then(result=>{
console.log(result);
}),10000000000);
});

The main problem here is that this implementation is flawed, because if your node application is restarted during the 24-hour window, your timeout will disappear (is an in memory object, not persisted) and the token will remain active, exposing you to security risks.
Manual token verification
A very common solution is to save the token_expiration_date alongside the token, making a date comparison during the related password reset request. If the token_expiration_date is expired, the request return an error and the server must delete the token on the db.
You can also make the opposite: store the token_creation_date and the max-token-ttl in your app code (e.g. 24 hours). In any case you make the date comparison at request time.
#NikKyriakides suggested (see comments) a more sophisticated version of this approach: you create a single JWT token that contains itself the expiration date. When the user request the reset password page you need only to verify if the token is valid calling a single method (no manual date comparison).
The Mongo expire option
A more elegant and effective solution is to create a different mongoose schema for your forgetpassword_token and use the native mongo/mongoose expire option to auto delete documents after a fixed time from their creation.
const secondsInADay = 60 * 60 * 24;
const tokenSchema = mongoose.Schema({
value: String
}, {timestamps: true});
tokenSchema.index({createdAt: 1},{expireAfterSeconds: secondsInADay});
const Token = mongoose.model('Token', tokenSchema);
Then add to your existing CompanySchema a reference to this schema:
forgetpassword_token: {type: mongoose.Schema.Types.ObjectId, ref: 'Token'}
A lot of question exists on this topic, so please also check them alongside with the related mongoose documentation.
The job scheduler
Another approach is to use a job scheduler like agenda to hourly check for expired tokens and delete them. Yes, you can write a setTimeout based check as a module for your app, but if the right tools exists yet, why don't use it? Also check #NikKyriakides comments below for potential drawbacks of this solution.

Related

MongoDB, there is no info online about transactions and read operations interaction

I'm trying to get the full picture...
When I create a session, I know all write operations that are associated with this session will either succeed together or roll back together.
I didn't find any official mongo documentation that explains what transactions lock exactly and when does the lock occur during the lifetime of the transaction (by lock I refer to both pessimistic lock or optimistic lock)
post here seems to be based on the assumption that a lock on a document starts after it's been updated and released at the end of the session.
But does the document even needs to lock? Does it indeed lock in that instant? Where can I find documentation on that?
That means that if I do
const person = await findOne({ id },{ session })
const updatedPerson = await updateOne({ id },{ person },{ session , new: true})
There is absolutely no meaning for session being on findOne? Because the specific person document doesn't get locked?
So if between me finding the person and updating the person, some other request have updated Person, updatedPerson could actually be different than person, is that correct? There is no mongoDB built in way with sessions to ensure person will be locked? ( I know there is a schema option for optimisticConcurrency, but I want to understand sessions, and also this option seems to be limited to only throwing an error instead of retrying which seems a bit odd considering usually the behavior you want with optimisticConcurrency is to retry or atleast have the option to)
If that's correct, then the only reason for session to be on strictly read operations would be to be able to view write results that are part of the session.
const updatedPerson = await updateOne({ id },{ field1: 'changed' },{ session , new: true})
const person = await findOne({ id} ,{ session })
Associating person with session here lets me view the updatedPerson post update.
Am I correct in my understanding? If so, that leads me to the next question, specifically on mongoose with .save(). According to mongoose documentation
For example, if you're using save() to update a document, the document can change
in MongoDB in between when you load the document using findOne() and when you
save the document using save() as show below. For many use cases, the save() race
condition is a non-issue. But you can work around it with findOneAndUpdate() (or
transactions) if you need to.
Which raises my question, how can you fix save() race condition with transactions considering transactions do not lock read documents?
I did some manual testings using await new Promise((r) => setTimeout(r, 5000)); and updating the document while a session is ongoing on it to observe its behavior.
My findings are as follows:
In this example:
const person = await findOne({ id },{ session })
const updatedPerson = await updateOne({ id },{ $set: { ...person } },{ session , new: true})
There is meaning for the session on findOne, even though the findOne operation doesn't lock the document, it causes updateOne to fail and abort the transaction if the document that was fetched by findOne , in our case person, was changed by something that is not part of the transaction.
That means you can trust that updatedPerson will be person because person is part of the session. This answer renders the rest of my question irrelevant.

what is the difference between document middleware, model middleware, aggregate middleware, and query middleware?

I am fairly new to MongoDB and Mongoose, I am really confused about why some of the middleware works at the document and some works on query. I am also confused about why some of the query methods return documents and some return queries. If a query is returning document it is acceptable, but why a query return query and what really it is.
Adding more to my question what is a Document function and Model or Query function, because both of them have some common methods like updateOne.
Moreover, I have gathered all these doubts from the mongoose documentation.
Tl;dr: the type of middleware most commonly defines what the this variable in a pre/post hook refers to:
Middleware Hook
'this' refers to the
methods
Document
Document
validate, save, remove, updateOne, deleteOne, init
Query
Query
count, countDocuments, deleteMany, deleteOne, estimatedDocumentCount, find, findOne, findOneAndDelete, findOneAndRemove, findOneAndReplace, findOneAndUpdate, remove, replaceOne, update, updateOne, updateMany
Aggregation
Aggregation object
aggregate
Model
Model
insertMany
Long explanation:
Middlewares are nothing, but built-in methods to interact with the database in different ways. However, as there are different ways to interact with the database, each with different advantages or preferred use-cases, they also behave differently to each other and therefor their middlewares can behave differently, even if they have the same name.
By themselves, middlewares are just shorthands/wrappers for the mongodbs native driver that's being used under the hood of mongoose. Therefor, you can usually use all middlewares, as if you were using regular methods of objects without having to care if it's a Model-, Query-, Aggregation- or Document-Middleware, as long as it does what you want it to.
However, there are a couple of use-cases where it is important to differentiate the context in which these methods are being called.
The most prominent use-case being hooks. Namely the *.pre() and the *.post() hooks. These hooks are methods that you can "inject" into your mongoose setup, so that they are being executed before or after specific events.
For example:
Let's assume I have the following Schema:
const productSchema = new Schema({
name: 'String',
version: {
type: 'Number',
default: 0
}
});
Now, let's say you always want to increase the version field with every save, so that it automatically increases the version field by 1.
The easiest way to do this would be to define a hook that takes care of this for us, so we don't have to care about this when saving an object. If we for example use .save() on the document we just created or fetched from the database, we'd just have to add the following pre-hook to the schema like this:
productSchema.pre('save', function(next) {
this.version = this.version + 1; // or this.version += 1;
next();
});
Now, whenever we call .save() on a document of this schema/model, it will always increment the version before it is actually being saved, even if we only changed the name.
However, what if we don't use the .save() or any other document-only middleware but e.g. a query middleware like findOneAndUpdate() to update an object?
Then, we won't be able to use the pre('save') hook, as .save() won't be called. In this case, we'd have to implement a similar hook for findOneAndUpdate().
Here, however, we finally come to the differences in the middlewares, as the findOneAndUpdate() hook won't allow us to do that, as it is query hook, meaning it does not have access to the actual document, but only to the query itself. So if we e.g. only change the name of the product the following middleware would not work as expected:
productSchema.pre('findOneAndUpdate', function(next) {
// this.version is undefined in the query and would therefor be NaN
this.version = this.version + 1;
next();
});
The reason for this is, that the object is directly updated in the database and not first "downloaded" to nodejs, edited and "uploaded" again. This means, that in this hook this refers to the query and not the document, meaning, we don't know what the current state of version is.
If we were to increment the version in a query like this, we'd need to update the hook as follows, so that it automatically adds the $inc operator:
productSchema.pre('findOneAndUpdate', function(next) {
this.$inc = { version: 1 };
next();
});
Alternatively, we could emulate the previous logic by manually fetching the target document and editing it using an async function. This would be less efficient in this case, as it would always call the db twice for every update, but would keep the logic consistent:
productSchema.pre('findOneAndUpdate', async function() {
const productToUpdate = await this.model.findOne(this.getQuery());
this.version = productToUpdate.version + 1;
next();
});
For a more detailed explanation, please the check the official documentation that also has a designated paragraph for the problem of having colliding naming of methods (e.g. remove() being both a Document and Query middleware method)

Mongoose update middleware - need to create hooks for every single update middleware?

Let's say I have the following schema:
PersonSchema = {
name: String,
timesUpdated: {
type: Number,
default: 0
}
}
Every time that the given person is updated, I would want the timesUpdated field to increment by one. Now, I could use Mongoose's update middleware hook, which would be called by something like
PersonModel.update({_id: <id>}, {name: 'new name'})
and my timesUpdated field would be appropriately incremented. However, if I only wrote a hook for the update middleware, the following code would not update my timesUpdated field:
PersonModel.updateOne({_id: <id>}, {name: 'new name'})
In order for my count to be updated, I would have to write middleware for the udpateOne query. This pattern repeats for several other similar middleware hooks, such as updateMany, replaceOne, save (if you want to update a document this way), findOneAndUpdate and I'm sure many others.
I use the example of an updated count for simplicity, but I could also have used an example where some other unrelated action happens upon changing my name. Am I missing something in how hooks should be used, or is this a limitation of mongoose hooks?
Pre save hook will only be executed with following functions according to mongoose's middleware document.
init
validate
save
remove
However update functions are working directly with MongoDB, therefor there is no general use hook applies on all update functions. See related discussion on Github.
I'd suggest using a function to perform your task before/after all required calls (to update or updateOne) rather a hook, because of the limitations mentioned in the other answer and the question.
Or perhaps limit the kinds of methods that can be called to the ones that have the hook set.
Or use a hook which will always get called in the middle-ware sequence, like a validate hook.

Preventing concurrent access to documents in Mongoose

My server application (using node.js, mongodb, mongoose) has a collection of documents for which it is important that two client applications cannot modify them at the same time without seeing each other's modification.
To prevent this I added a simple document versioning system: a pre-hook on the schema which checks if the version of the document is valid (i.e., not higher than the one the client last read). At first sight it works fine:
// Validate version number
UserSchema.pre("save", function(next) {
var user = this
user.constructor.findById(user._id, function(err, userCurrent) { // userCurrent is the user that is currently in the db
if (err) return next(err)
if (userCurrent == null) return next()
if(userCurrent.docVersion > user.docVersion) {
return next(new Error("document was modified by someone else"))
} else {
user.docVersion = user.docVersion + 1
return next()
}
})
})
The problem is the following:
When one User document is saved at the same time by two client applications, is it possible that these interleave between the pre-hook and the actual save operations? What I mean is the following, imagine time going from left to right and v being the version number (which is persisted by save):
App1: findById(pre)[v:1] save[v->2]
App2: findById(pre)[v:1] save[v->2]
Resulting in App1 saving something that has been modified meanwhile (by App2), and it has no way to notice that it was modified. App2's update is completely lost.
My question might boil down to: Do the Mongoose pre-hook and the save method happen in one atomic step?
If not, could you give me a suggestion on how to fix this problem so that no update ever gets lost?
Thank you!
MongoDB has findAndModify which, for a single matching document, is an atomic operation.
Mongoose has various methods that use this method, and I think that they will suit your use case:
Model.findOneAndUpdate()
Model.findByIdAndUpdate()
Model.findOneAndRemove()
Model.findByIdAndRemove()
Another solution (one that Mongoose itself uses as well for its own document versioning) is to use the Update Document if Current pattern.

using _.omit on mongoose User in node.js

I have a mongoose User schema built like this:
var UserSchema = new Schema({
username: { type: String, required: true, index: { unique: true } },
password: { type: String, required: true },
salt: { type: String, required: true}
});
I want to be able to send this user object to the client side of my application but I don't want to sned the password or salt fields.
So I added he following code to my user model module
U
serSchema.methods.forClientSide = function() {
console.log('in UserSchema.methods.forClientSide');
console.log(this);
//var userForClientSide=_.omit(this,'passsword','salt');
var userForClientSide={_id:this._id, username:this.username };
console.log(userForClientSide);
return userForClientSide;
}
I have required the underscore module (its installed locally via a dependency in my package.js).
not the commented out line - I was expecting it to omit the password and salt fields of the user object but it did not do anything :( the logged object had the full set of properties.
when replaced with the currently used like var userForClientSide={_id:this._id, username:this.username }; it gets the results I want but:
1) I want to know why does the _.omit not work.
2) I don't like my current workaround very much because it actually selects some properties instead of omitting the ones I don't like so if I will add any new propertes to the scema I will have to add them here as well.
This is my first attempt at writing something using node.js/express/mongodb/mongoose etc. so It is very possible hat I am missing some other better solution to this issue (possibly some feature of mongoose ) feel free to educate me of the right way to do things like this.
so basically I want to know both what is the right way to do this and why did my way not work.
thanks
1) I want to know why does the _.omit not work.
Mongoose uses defineProperty and some heavy metaprogramming. If you want to use underscore, first call user.toJSON() to get a plain old javascript object that will work better with underscore without all the metaprogramming fanciness, functions, etc.
A better solution is to use mongo/mongoose's fields object and pass the string "-password -salt" and therefore just omit getting these back from mongo at all.
Another approach is to use the mongoose Transform (search for "tranform" on that page). Your use case is the EXACT use case the documentation uses as an example.
You can also make your mongoose queries "lean" by calling .lean() on your query, in which case you will get back plain javascript objects instead of mongoose model instances.
However, after trying each of these things, I'm personally coming to the opinion that there should be a separate collection for Account that has the login details and a User collection, which will make leaking the hashes extremely unlikely even by accident, but any of the above will work.

Resources