How to initiate serializeUser method when user data has changed? - mongodb

I use passportjs with passport-local strategy to authenticate users in my project. Official serializeUser deserializeUser approach is the following:
// serialize and deserialize
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user){
done(err, user.toJSON());
})
});
But due to performance reason I need to prevent query User.findById to my MongoDB database so I use the following approach:
passport.serializeUser(function(user, done){
done(null, user.toJSON());
});
passport.deserializeUser(function(user, done){
done(null, user);
});
But now I came up with the following problem: what if user change their data like name, age etc. How how could I update these without logout?
Actually I need to execute passport.serializeUser manually some how?

If you're using a database session store then you're still just retrieving the entire user.toJSON() from database upon every deserializeUser call, something you thought you were avoiding but actually are not.
If you're not using a database session store, then you may be storing it all in a cookie or something which seems highly unsafe, as user object may contain sensitive information like password-hash.
If you just want to store users in memory, you should do just that. Create a cache that holds user objects.
var Users = {};
passport.serializeUser(function(user, done) {
Users[user._id] = user; // store in cache
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
if(Users[id]) return done(null, Users[id]); // retrieve from cache
User.findById(id, function(err, user){
if(err) return done(err);
Users[id] = user; // store in cache now if wasn't already found
done(null, user);
});
});

Related

Meteor check whether document already exists in collection or not?

I am beginner in meteor. I have a form having username and password as input fields and a submit button in the end.
I have correctly collected data from both fields into two variables. Now what I want is to verify whether any matching document exists in my MongoDB collection or not? My below code is not working. How to do it? Please help. Here is my code.
Template.form.events({
'submit.login':function(event){
event.preventDefault();
var user = document.getElementById("myForm").elements[0].value;;
var pass = document.getElementById("myForm").elements[1].value;
var usernamee = (Collection.Login.find({username: user},{password: pass})).count();
if(usernamee>0) {
alert("found");
} else {
alert("not found");
}
return false;
}
});
Firstly your .find() is incorrect:
var usernamee = (Collection.Login.find({username: user},{password: pass})).count();
shoud be:
var usernamee = (Collection.Login.find({username: user, password: pass})).count();
Assuming that you're publishing that collection to the client either with autopublish or an explicit publication.
However:
You are giving even non-logged in users access to the usernames and cleartext passwords of all other users!
Meteor includes the accounts package that takes care of user management for you. You don't need to reinvent the wheel. You want to take advantage of the security work that's already been done for you.
You can use a method call to find out if a username has already been used and warn the new user in the UI before they create their account.
client:
Meteor.call('usernameExists', username, function(err, result){
if (result) {
alert('Username '+username+' is already taken!')
// clear out the form etc...
}
});
server:
Meteor.methods({
usernameExists(username){
return Meteor.users.findOne({username}) !== 'undefined';
}
});

Should I use information stored in a cookie for indexing purposes?

I am creating my first major app, and I thought of a way to optimize query performance. I am not sure though whether I should go through with it.
Here is a description of my approach.
Every time a user account is created, that user is assigned a random number between 1 and 10, which is stored in their user document. The number is called a number-Id. Here is how the user schema will look like:
let User = new Schema({
/// I left out all the other fields for clairty sake
numberId: {
type: Number,
default: Math.floor(Math.random() * 11),
index: true
}
}
Every time a user creates a blogpost and post, their number-Id is referenced inside the document of that blogpost and post. This is to make querying much faster by indexing the users number-id. Here is how the document of a blogpost would look like in MongoD:
{
"title": "my Blog Post",
"_id": "ObjectId("594824b2828d7b15ecd7b6a5")",
/// Here is the numberId of the user who posted the blogpost, it is added
/// to the document of the blogpost when it is created.
"postersNumberId": 2
/// the Id of the user who posted the blogpost
"postersId": "59481f901f0c7d249cf6b050"
}
Let's say I want to get all the blogposts made by a specific user. I can optimize my query much faster by using the number-Id of the user in question as an index, given that their number-Id is referenced in all the blogposts and comment posts they make.
BlogPost.find({postersId: user_id, postersNumberId: user.numberId});
It seems like this approach warrants that I store the users number-id in req.user in order for it to be readily available whenever I need it to optimize queries. So that means I would have to store the users data in a cookie via passport:
passport.serializeUser(function(user, done){
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user){
if (err || !user) return done(err, null);
done(null, user);
});
});
Given this approach, I could now use all the information stored in the cookie, particularly the numberId, to optimize queries that retrieve the comments and blogposts a user makes:
BlogPost.find({postersId: req.user_id, postersNumberId: req.user.numberId});
However, I am using json-web-tokens to authenticate the user rather than cookies. So I will have to use a cookie to store the number-Id for indexing purposes in addition to using JWT for authentication. I've heard, however, that having cookies is bad for scalability, so I am worried that storing the users number-Id in req.user will eventually impact performance.
Should I continue with this approach, or no? What are the performance implications?
In addition to authentication JWT has a payload, which can be used to store additional information within the generated token itself:
var jwt = require('jsonwebtoken');
var token = jwt.sign({
data: {
numberId: 7
}
}, 'jwtSecret', {
expiresIn: '1h'
});
For retrieval:
jwt.verify(token, 'jwtSecret', function(err, decoded) {
if (err) {
console.log(err)
} else {
console.log(decoded);
//{ data: { numberId: 7 }, iat: 1498350787, exp: 1498354387 }
}
});

Make user settings available in req

I know that passport exposes the current user (I think using the 'passport' policy) in req.user. I want to do a similar thing for settings of a particular user, which are stored in a separate collection (so that they are available in req.settings). How can I do this?
The serializeUser method is responsible for that https://github.com/jaredhanson/passport#sessions
passport.serializeUser(function(user, done) {
done(null, user.id);
});
So instead of just serializing the user.id you could serialize the the settings, too:
passport.serializeUser(function(user, done) {
var sessionUser = { _id: user._id, name: user.name, settings: [...] }
done(null, sessionUser);
});
You might want to read Safe to store complete user info in session with Sails.js?

Cascade style delete in Mongoose

Is there a way to delete all children of an parent in Mongoose, similar to using MySQLs foreign keys?
For example, in MySQL I'd assign a foreign key and set it to cascade on delete. Thus, if I were to delete a client, all applications and associated users would be removed as well.
From a top level:
Delete Client
Delete Sweepstakes
Delete Submissions
Sweepstakes and submissions both have a field for client_id. Submissions has a field for both sweepstakes_id, and client_id.
Right now, I'm using the following code and I feel that there has to be a better way.
Client.findById(req.params.client_id, function(err, client) {
if (err)
return next(new restify.InternalError(err));
else if (!client)
return next(new restify.ResourceNotFoundError('The resource you requested could not be found.'));
// find and remove all associated sweepstakes
Sweepstakes.find({client_id: client._id}).remove();
// find and remove all submissions
Submission.find({client_id: client._id}).remove();
client.remove();
res.send({id: req.params.client_id});
});
This is one of the primary use cases of Mongoose's 'remove' middleware.
clientSchema.pre('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
Sweepstakes.remove({client_id: this._id}).exec();
Submission.remove({client_id: this._id}).exec();
next();
});
This way, when you call client.remove() this middleware is automatically invoked to clean up dependencies.
In case your references are stored other way around, say, client has an array of submission_ids, then in a similar way as accepted answer you can define the following on submissionSchema:
submissionSchema.pre('remove', function(next) {
Client.update(
{ submission_ids : this._id},
{ $pull: { submission_ids: this._id } },
{ multi: true }) //if reference exists in multiple documents
.exec();
next();
});
which will remove the submission's id from the clients' reference arrays on submission.remove().
Here's an other way I found
submissionSchema.pre('remove', function(next) {
this.model('Client').remove({ submission_ids: this._id }, next);
next();
});
I noticed that all of answers here have a pre assigned to the schema and not post.
my solution would be this: (using mongoose 6+)
ClientSchema.post("remove", async function(res, next) {
await Sweepstakes.deleteMany({ client_id: this._id });
await Submission.deleteMany({ client_id: this._id });
next();
});
By definition post gets executed after the process ends pre => process => post.
Now, you're probably wondering how is this different than the other solutions provided here.
What if a server error or the id of that client was not found?
On pre, it would delete all sweeptakes and submissions before the deleting process start for client. Thus, in case of an error, it would be better to cascade delete the other documents once client or the main document gets deleted.
async and await are optional here. However, it matters on large data. so that the user wouldn't get those "going to be deleted" cascade documents data if the delete progress is still on.
At the end, I could be wrong, hopefully this helps someone in their code.
Model
const orderSchema = new mongoose.Schema({
// Множество экземпляров --> []
orderItems: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItem',
required: true
}],
...
...
});
asyncHandler (optional)
const asyncHandler = fn => (req, res, next) =>
Promise
.resolve(fn(req, res, next))
.catch(next)
module.exports = asyncHandler;
controller
const asyncHandler = require("../middleware/asyncErrHandler.middleware");
// **Models**
const Order = require('../models/order.mongo');
const OrderItem = require('../models/order-item.mongo');
// #desc Delete order
// #route DELETE /api/v1/orders/:id
// #access Private
exports.deleteOrder = asyncHandler(async (req, res, next) => {
let order = await Order.findById(req.params.id)
if (!order) return next(
res.status(404).json({ success: false, data: null })
)
await order.remove().then( items => {
// Cascade delete -OrderItem-
items.orderItems.forEach( el => OrderItem.findById(el).remove().exec())
}).catch(e => { res.status(400).json({ success: false, data: e }) });
res.status(201).json({ success: true, data: null });
});
https://mongoosejs.com/docs/api/model.html#model_Model-remove

how to check whether username and password exists inside database(mongodb)

i want to store username and password inside mongodb database and later on retrieve database values and check whether the username and password exists inside database.If it does exist then i would redirect to another page.How can i achieve this using node.js and mongodb.I am able to store the values inside database.But getting confused for how to fetch the values and check them against the values provide in form field values.There is no method in mongodb like fetchByName or something similar.
Can someone help me out with the code.
I think you should take a look on the Nodepad source code, it explains very well how to achieve this with Mongoose:
User.virtual('password')
.set(function(password) {
this._password = password;
this.salt = this.makeSalt();
this.hashed_password = this.encryptPassword(password);
})
.get(function() { return this._password; });
User.method('authenticate', function(plainText) {
return this.encryptPassword(plainText) === this.hashed_password;
});
User.method('makeSalt', function() {
return Math.round((new Date().valueOf() * Math.random())) + '';
});
User.method('encryptPassword', function(password) {
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
});
User.pre('save', function(next) {
if (!validatePresenceOf(this.password)) {
next(new Error('Invalid password'));
} else {
next();
}
});
try using an already existing library like passport or everyauth. There are other ones too, google them :)