Sailsjs: How to populate association after *update* to model? - node.js

I have the following controller code that works for index, show, create methods but the update fails when I include populate - what am I doing wrong?
// User List
index: function(req, res) {
User.find()
.populate('profile')
.exec(function(err, users) {
if (err) return res.json(err, 400);
if (!users) return res.json(users, 404);
res.json(users, 200);
});
},
// Single User
show: function(req, res) {
User.findOne({ username: req.param('username') })
.populate('profile')
.exec(function(err, user) {
if (err) return res.json(err, 400);
if (!user) return res.json(user, 404);
res.json(user, 200);
});
},
// Create User
create: function(req, res) {
User.create(req.body, function(err, user) {
if (err) return res.json(err, 400);
Person.create({user: user.id, slug: user.username}, function(err, profile) {
if (err) return res.json(err, 400);
User.update(user.id, {profile: profile.id})
.populate('profile')
.exec(function(err, user) {
if (err) return res.json(err, 400);
});
user.profile = profile;
res.json(user, 200);
});
});
},
// Update User
update: function(req, res) {
var username = req.param('username');
User.update({ username: username }, req.body)
.populate('profile')
.exec(function(err, user) {
if (err) return res.json(err, 400);
res.json(user, 201);
});
},

As per the documentation the update function takes a callback to which it passes the updated records.
Example from the doc :
// For example, to update a user's name,
// .update(query, params to change, callback)
User.update({
name: 'sally'
},{
phone: '555-555-5555'
}, function(err, users) {
// Error handling
if (err) {
return console.log(err);
// Updated users successfully!
} else {
console.log("Users updated:", users);
}
});
Applied to your code, it would look like this :
// Update User
update: function(req, res) {
var username = req.param('username');
User.update({ username: username }, req.body)
.exec(function(err, users) {
if (err) {
return res.json(err, 400);
}
var user = users.slice(0,1); // Take the first user in the array
User.findOne(user.id) // You may try something like User._model(user) instead to avoid another roundtrip to the DB.
.populate('profile')
.exec(function(err, user) {
if (err) {
return res.json(err, 400);
}
res.json(user, 201);
});
});
}

Jeremie your answer is correct in essence but has a few issues:
the user object returned by the update call and then sliced does not
have a populate function
slice the users list returns a list instead of the object desired
Personal preference but I also prefer the following which uses the exec call instead of the callback.
update: function(req, res) {
var id = req.param('id');
User
.update(id, req.params.all())
.exec(function(err, users) {
if(err) return res.json(err, 400);
var user = users[0];
console.log('ID', user.id);
User
.findOne(user.id)
.populate('profile')
.exec(function (err, user){
if (err) return res.json(err, 400);
res.json(user, 201);
});
});
},

Related

How to disallow disabled users from logging in, into a page using passport.js?

I've used a soft delete feature in mongodB to disable a user and not delete it permanently. Now, only enabled users can log in to the site while the disabled users can not log in to the site.
// Soft delete feature
exports.getDisabledUsers = function(req,res, next) {
User.find({active: false}, function(err, users) {
if (err) {
res.send(err);
}
res.json(users);
});
}
// Log In
exports.login = function (req, res, next) {
var userInfo = setUserInfo(req.user);
res.status(200).json({
token: 'JWT ' + generateToken(userInfo),
user: userInfo
});
};
// Passport.js
var localLogin = new LocalStrategy(localOptions, function (email, password, done) {
User.findOne({
email: email
}, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
error: 'Login failed. Please try again.'
});
}
user.comparePassword(password, function (err, isMatch) {
if (err) {
return done(err);
}
if (!isMatch) {
return done(null, false, {
error: 'Login failed. Please try again.'
});
}
return done(null, user);
});
});
});
Disabled users shouldn't be able to log in. Right now, they are able to log in.
While Disabling the user you can move that data to another collection, that way your disabled user cannot be able to login to your system
exports.disableUser = function (req, res, next) {
User.findByIdAndUpdate({_id:req.params._id}, {active: false}, function (err, users) {
if (err) {
res.send(err);
}
User.findByIdAndRemove(
{_id:req.params._id},
function(err, doc) {
if (doc) {
DisableUser.insertMany(doc, function(err, doc){
// your response
});
} else {
// your response
}
)
});
}
You have not checked user status is active or not. This can be done by adding a condition in code, or modifying mongo query and adding one more condition in findOne.
//CODE
if(!user.active) {
//RETURN USER IS NOT ACTIVE
}
//MONGO QUERY
User.findOne({
email: email,
active: true
}, function (err, user) {
//YOUR CODE
}
Role based login may create some complexity but this can be achieved simply by adding one more collection named disabledUsers
when admin disable any user then the click function do two things at the same time
1)add the email into disabledUser collection
2)delete the user from user collection
Then in your login function just check this way
disabledUser.findOne({email :email },
function (err ,user){
if(err) return done(err)
else{
if(!user){
user.findOne({email : email }
write your login stuff here the wau you r doing
}
else{return alert('user is disabled')}
}
})

Hashing password before update a user in mongoose

I create the user, hash his password and save on mongo. My problem begins when I try to update that user. For now, when I update the hash isn't generated, cause I really don't know how to do it.
The middleware to get the user that I'm talking about:
exports.userByID = function(req, res, next, id) {
User.findOne(
{
_id: id
},
function(err, user) {
if (err) {
return next(err);
} else {
req.user = user;
next();
}
}
);
};
The user controller, to update an user:
exports.update = async function(req, res, next) {
User.findByIdAndUpdate(req.user.id, req.body, function(err, user) {
if (err) {
return next(err);
} else {
res.json(user);
}
});
};
The pre 'save' on User's model:
UserSchema.pre("save", function(next) {
var user = this;
if (user.password) {
var md5 = crypto.createHash("md5");
user.password = md5.update(user.password).digest("hex");
console.log("Password após o save (hasheando):" + user.password);
}
next();
});
I'm using passport authentication ('local'). Already tried user.save() on the controller update:
user.save();
res.json(user);
But, without success.
This is may be because you are not storing the new_password in the mongo.
In update controller you have to do like this:
User.findByIdAndUpdate(req.user.id, req.body, function (err, user) {
if (err) {
return next(err);
} else {
user.password = req.body.new_password;
user.save(function (err, user) {
if (err) {
res.send("Error: ", err);
} else {
res.send("password updated successfully!");
}
})
}
});
Before saving the password just hash it and update it in DB. it will be something like below.
exports.update = async function(req, res, next) {
let { body} = req;
if(body['password']){
var md5 = crypto.createHash("md5");
body['password']= md5.update(body['password']).digest("hex");
}
let updateUser = await User.findByIdAndUpdate(req.user.id, body)
};

PassportJS: Edit user info

I'm using PassportJS. I'm trying to let users edit their email address if needed. This is the code I have which is currently not working.
Any advice? Thank you!
app.post("/editprofile", middleware.isLoggedIn, function(req, res, next){
User.update({ id: req.session.passport.user }, {
email: req.body.email,
}, function(err, user) {
if (err) return next(err);
User.findById(req.user._id, function(err, user) {
if (err) return next(err);
console.log(err)
return res.render('landing.ejs', {
user:user
});
});
});
});
Consider using this and every thing will be fine
app.post("/editprofile", middleware.isLoggedIn, function(req, res, next){
User
.findOneAndUpdate({ _id: request.session.passport.user }, req.body.email)
.exec(function(err, user) {
if (err) return res.render(/* Your error template here */, {
err: err.message
};
return res.render('landing.ejs', {
user: user
});
});
}
}
Hope this helps!

Unable to verify hashed password

Hi All,
I am authenticating my user using bcrypt module.
I am able to do perform the Registration process, but facing problem during Login process.
User Model:
var userSchema = new Schema({
email: {type: String, required: true},
password: {type: String,
});
Hashing methods:
userSchema.methods.encryptPassword = function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(5), null)
};
userSchema.methods.validPassword = function (password) {
return bcrypt.compareSync(password, this.password);
};
Sign in:
module.exports.login = function (user, callback) {
User.findOne({'email': user.email, 'password': user.validPassword(this.password)}, callback);
};
Login Route
router.post('/login', function (req, res) {
var user = req.body;
User.login(user, function (err, user) {
if (err) {
throw err;
}
if (!user) {
res.sendStatus(404);
return;
}
res.json(user.id);
});
});
While executing am getting this error: TypeError:user.validPassword is not a function
Please Help.
Your mistake is that the user being provided to your login method is not a Mongoose DB object. Instead, your login function should look something like this:
module.exports.login = function (request, callback) {
User.findOne({'email': request.email }, function(err, user) {
if (err) return callback(err);
if(!user || !user.validPassword(request.password)) return callback();
return callback(null, user);
});
};
This will ensure that user is a valid Mongoose object before you attempt to verify the password.
One other possible solution, if you'd prefer to avoid checking that the password is valid in your data layer, is to simply fetch the user document based on its email and then check the password in the login route.
router.post('/login', function (req, res) {
var user = req.body;
User.findOne(user, function (err, user) {
if (err) {
throw err;
}
if (!user) {
res.sendStatus(404);
return;
}
if (!user.validPassword(req.body.password)) {
res.sendStatus(401);
return;
}
res.json(user.id);
});
});
In Login Route, you need to instantiate the Schema:
router.post('/login', function (req, res) {
var user = new User(req.body);
User.login(user, function (err, user) {
if (err) {
throw err;
}
if (!user) {
res.sendStatus(404);
return;
}
res.json(user.id);
});
});

Modify req.user or use req.account?

I'm developing a mean application with passport, and I'm running through this issue:
I have a LocalStrategy to log on the user based on the application database. I need, however to login the user simultaneously on another service with possible multiple accounts. The thing is, once I route to authorize these logins, and set the variables to req.account, I cannot access them in other routes. Note that I can get the data I want, I just want to access it from somewhere other than this route, like req.user. I will post some of my code to clarify the situation.
Local Login route
app.post('/login', function (req, res, next) {
passport.authenticate('local-login', function (err, user) {
if (err)
return next(err);
if (!user)
return res.status(400).json({status: 'Invalid Username'});
req.login(user, function (err) {
if (err)
return next(err);
res.status(200).json({status: 'User successfully authenticated'});
});
})(req, res, next);
});
Local login passport config
passport.use('local-login', new LocalStrategy(function (user, pswd, done) {
User.findOne({'username': user}, function (err, user) {
if (err)
return done(err);
if (!user || !user.validPassword(pswd))
return done(null, false);
return done(null, user);
});
}));
The other service passport config
passport.use('other-login', new OtherStrategy(function (docs, done) {
if (docs.length === 0)
return done(null, false);
var accounts = [];
var user, pswd, data;
var counter = docs.length;
for (var i = 0; i < docs.length; i++) {
user = docs[i]._id;
pswd = docs[i].password;
request.post(<serviceurl>, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: qs.stringify({
grant_type: 'password',
username: user,
password: pswd,
client_id: process.env.API_KEY
})
}, function (err, res, body) {
if (err)
return done(err);
data = JSON.parse(body);
data.username = docs[docs.length - counter]._id;
accounts.push(data);
counter--;
if (counter === 0)
return done(null, accounts);
});
}
}));
Other Service route
router.get('/otherservice', passport.authorize('other-login', {}) , function (req, res) {
console.log(req.account);
res.sendStatus(200);
});
Other Service authentication (from custom Strategy)
ServiceStrategy.prototype.authenticate = function (req) {
var self = this;
var id = req.user.master_id || req.user.id;
Service.find({master_id: id}, function (err, docs){
if (err)
return self.error(err);
function verified(err, data, info) {
if (err) { return self.error(err); }
if (!data) { return self.fail(info); }
self.success(data, info);
}
try {
if (self._passReqToCallback) {
self._verify(req, docs, verified);
} else {
self._verify(docs, verified);
}
} catch (ex) {
return self.error(ex);
}
});};
I found the solution! On the User Model, I added an accounts property to store the data returned on the authorization. Then, on the authorization route, I updated the user with this info, and saved. It wasn't that hard at all.
app.post('/api/login', function (req, res, next) {
passport.authenticate('local-login', function (err, user) {
if (err)
return next(err);
if (!user)
return res.status(400).json({status: 'Invalid Username'});
req.login(user, function (err) {
if (err)
return next(err);
var id = req.user.master_id || req.user.id;
Service.findOne({master_id: id}, function (err, doc) {
if (doc == null)
res.status(200).json({
status: 'User successfully authenticated',
accounts: false
});
else
return next();
});
});
})(req, res, next);
}, passport.authorize('other-login', {}), function (req, res) {
var accounts = req.account;
var user = req.user;
user.accounts = accounts;
user.save(function (err, newUser) {
if (err)
throw err;
res.status(200).json({
status: 'User sucessfully authenticated',
accounts: true
});
})
});

Resources