Save a Mongo document with exception - javascript

In a mean stack app I create a new user in User Schema then a new doc in Doc Schema
var UserSchema = new Schema({
username: String,
password: String,
docs: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Doc'
}],
)
}
var DocSchema = new Schema({…)
}
UserSchema.pre('save', function(next) {
if (this.password) {
this.salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64');
this.password = this.hashPassword(this.password);
}
next();
});
The following parts of code is the passport signup where at the end I have an issue with newUser.save(); After the push method if I do not save the user, doc ID are not displayed in user document. But saving user seems to change the hashed password also. If I comment newUser.save(); login works fine otherwise I get a wrong password
passport.use('local-signup', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true
},
function(req, username, password, done) {
process.nextTick(function() {
User.findOne({
username: username
}, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('signupMessage', 'Username is already taken.'));
} else {
var newUser = new User();
newUser.username = username;
newUser.password = password;
newUser.email = req.body.email;
newUser.save(function(err) {
if (err)
throw err;
var doc = new Doc({
user: newUser.username,
docTitle: newUser.username
});
doc.save(function(err) { // create doc
if (err) {
return next(err);
} else {
newUser.docs.push(doc); // push doc'id in docs field in user
newUser.save(); // save user after doc'id has been push
};
return done(null, newUser);
});
});
Any help would be appreciated

Your logic in your mongoose pre save middleware says 'if there is a password on the document being saved, generate a salt and hash the password'. Therefore, if a password exists on the document that has already been salted and hashed, when the middleware runs it will salt and hash this pre-existing password again. This is why you can't login a second time; your password is changing every time you save the document.
I'm guessing you expect the mongoose pre save middleware to run only when you save the document the first time. The pre save middleware runs every time you save the document. There is a this.isNew property available on the document in the pre save middleware that you can make use of. This will ensure the password is only generated the first time you save the document.
UserSchema.pre('save', function(next) {
if (this.password && this.isNew) {
this.salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64');
this.password = this.hashPassword(this.password);
}
next();
});

Related

The right way to connect to query database mongodb multiples times

im building a website in my free time using nodejs/Handlebars.js/mongodb(mongoose),
i didnt study any web developement lessons, and i dont know the right way to do things efficiently and securely.
so in this project, i stumbled upon a problem where i had to query in the database if email already exists or no if it does, it queries again if the username already exists or not, if it does exists the user can be registred to the databse. yes it gets the job done but im not satisfyied with this approach, it seems to be unprofessional and not secure.
so can you please tell me the right way to do it ?
this is the part where i think i ve done it the wrong way
//check for errors in Req.validation and push them to errors Array
if(valErrors){
for (var i = 0; i < valErrors.length; i++) {
errors.push(valErrors[i])
}
}
//check if the username submitted exists in the database
User.findOne({'username':username}, function (err, user) {
if(user)
{
errors.push({msg:"username is already in use!"})
res.render('user/register',{
errors:errors
});
}
//if the username is not in use already check if the email is in
//use
else {
User.findOne({'email':email}, function (err, user) {
if(user){
errors.push({msg:'email is already in use !'})
res.render('user/register',{
errors:errors
});
} //if the email doesnt exists too then register this //user
else{
var coins = new Coins()
var newUser = new User({
name: name,
email:email,
username: username,
password: password,
coins:coins.encryptcoins('0'),
joindate:getDate()
});
User.createUser(newUser, function(err, user){
if(err) throw err;
});
req.flash('success_msg', 'You are registered and can now login');
res.redirect('/user/login');
}
});
}
});
})
EDIT:
user Schema
var mongoose = require('mongoose');
var bcrypt = require('bcryptjs');
// User Schema
var UserSchema = mongoose.Schema({
username: {
type: String,
index:true,
required:true
},
password: {
type: String,
required:true
},
email: {
type: String,
required:true
},
name: {
type: String,
required:true
},
coins: {
type:String,
required:true
},
joindate: {
type:String,
required:true
},
orders: {
type:Array,
required:false
}
},{collection:'Users'});
var User = module.exports = mongoose.model('User', UserSchema);
module.exports.createUser = function(newUser, callback){
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash) {
newUser.password = hash;
newUser.save(callback);
});
});
}
module.exports.getUserByUsername = function(username, callback){
var query = {username: username};
User.findOne(query, callback);
}
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
callback(null, isMatch);
});
}
this is the whole code
var express = require('express');
var router = express.Router();
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var User = require('../models/users');
const ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn();
const ensureLoggedOut = require('connect-ensure-login').ensureLoggedOut();
var Coins = require('../models/coins');
// Register
router.get('/register',ensureLoggedOut, function(req, res){
res.render('user/register');
});
// Login
router.get('/login',ensureLoggedOut, function(req, res){
res.render('user/login');
});
// Register User
router.post('/register', function(req, res){
var name = req.body.name;
var email = req.body.email;
var username = req.body.username;
var password = req.body.password;
var password2 = req.body.password2;
console.log(email)
console.log(username)
// Validation
req.checkBody('name', 'Name is required').notEmpty();
req.checkBody('email', 'Email is required').notEmpty();
req.checkBody('email', 'Email is not valid').isEmail();
req.checkBody('username', 'Username is required').notEmpty();
req.checkBody('password', 'Password is required').notEmpty();
req.checkBody('password2', 'Passwords do not match').equals(req.body.password);
//Error handling
var errors = [];
var valErrors = req.validationErrors()
//check for errors in Req.validation and push them to errors Array
if(valErrors){
for (var i = 0; i < valErrors.length; i++) {
errors.push(valErrors[i])
}
}
//check if the username submitted exists in the database
User.findOne({'username':username}, function (err, user) {
if(user)
{
errors.push({msg:"username is already in use!"})
res.render('user/register',{
errors:errors
});
}
//if the username is not in use already check if the email is in
//use
else {
User.findOne({'email':email}, function (err, user) {
if(user){
errors.push({msg:'email is already in use !'})
res.render('user/register',{
errors:errors
});
} //if the email doesnt exists too then register this //user
else{
var coins = new Coins()
var newUser = new User({
name: name,
email:email,
username: username,
password: password,
coins:coins.encryptcoins('0'),
joindate:getDate()
});
User.createUser(newUser, function(err, user){
if(err) throw err;
});
req.flash('success_msg', 'You are registered and can now login');
res.redirect('/user/login');
}
});
}
});
})
passport.use(new LocalStrategy(
function(username, password, done) {
User.getUserByUsername(username, function(err, user){
if(err) throw err;
if(!user){
return done(null, false, {message: 'Unknown User'});
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, user);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.getUserById(id, function(err, user) {
done(err, user);
});
});
router.post('/login',
passport.authenticate('local', {successReturnToOrRedirect: '/', failureRedirect:'/user/login',failureFlash: true}),
function(req, res) {
res.redirect('/');
});
router.get('/logout',ensureLoggedIn, function(req, res){
req.logout();
req.session.destroy();
res.redirect('/');
});
module.exports = router;
function getDate(){
var d = new Date()
return ("date: "+d.getDate()+"/"+(d.getMonth()+1)+"/" +d.getFullYear() + " time GMT+1: "+(d.getHours()+1)+":"+(d.getMinutes())).toString()
}
// replaced with Ensure loging in library !
// function ensureLoggedIn(req, res, next) {
// if(req.user){
// return next()
// }else{
// res.redirect('/user/login');
// }
// }
// function ensureLoggedOut(req, res, next) {
// if(!req.user){
// return next()
// }else{
// res.redirect('/');
// }
// }
In general, for a logical unit of work send to a Database Management System (DBMS) (i.e., MongoDB server), it is imperative to group the individual operations in a single transaction. This way, you can avoid inconsistencies that might result from concurrent user creation in your database.
To be more precise, in your project the registration process checks for the following:
Check if email exists
Check if username exists
If queries 1 and 2 returned an empty result set, register a new user
In essence, those 3 steps need to take place in an atomic fashion, which means that they occur as a single logical unit (Transaction). If not, in the extreme case that 2 concurrent clients try to register users with the same usernames, then your database will result with two users with the same username.
Therefore, you should update your code to do the following:
Initiate a transaction
Check for users with the given email (user_email) and or usernamae (user_name)
If the query of step 2 returned a user, then rollback the transaction; Otherwise, insert a new user with user_email and user_name.
Commit Transaction
I am not sure whether MongoDB supports Transactional Consistency, and this is one of the reasons that I suggested using an RDBMS. Also, if it doesn't, I am sure that you can figure out a schema that identifies a single user based on email/username and try to perform the registration as a transaction.
Finally, it is considered good practice to have most of the processing take place in the DBMS side with the use of Stored Procedures.
I hope this helps.

On post route send email with nodemailer and save user to mongodb with passport.authenticate

I am trying to send a welcome email after a user signs up with nodemailer and also adding the user to mongodb with passport.authenticate on the same post route. I am able to get this to work separately i.e. either sending email or adding the user to the database but can't seem to get them to work together. I am new to nodejs and would really appreciate any help. Here is the route I am trying to get to work:
router.post('/signup', function(req, res,next) {
async.waterfall([
function(done) {
passport.authenticate('signup', {
successRedirect: '/',
failureRedirect: '/signup',
failureFlash : true
});
},
function(user, done) {
var transporter = nodeMailer.createTransport({
service: 'SendGrid',
auth: {
user: 'user',
pass: 'password'
}
});
var mailOptions = {
to: user.email,
from: 'me#gmail.com',
subject: 'Welcome to the site',
html: '<p> This is html, did I render correctly?</p>'
};
transporter.sendMail(mailOptions, function(err){
done(err);
});
}
], function(err) {
res.redirect('/signup');
});
});
Here is the signup strategy with passport:
var LocalStrategy = require('passport-local').Strategy;
var User = require('../models/user');
var bCrypt = require('bcrypt-nodejs');
module.exports = function(passport){
passport.use('signup', new LocalStrategy({
usernameField : 'email',
passReqToCallback : true
},
function(req, email, password, done) {
findOrCreateUser = function(){
// find a user in Mongo with provided username
User.findOne({ 'email' : email }, function(err, user) {
// In case of any error, return using the done method
if (err){
req.flash('error','Email Already Exists',err.message);
return done(err);
}
// already exists
if (user) {
console.log('User already exists with username:');
return done(null, false, req.flash('error','Email Already Exists'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.password = createHash(password);
newUser.email = req.param('email');
newUser.firstName = req.param('firstName');
newUser.lastName = req.param('lastName');
// save the user
newUser.save(function(err) {
if (err){
console.log('Error in Saving user: '+err);
return done(null, false, req.flash('error',err.message));
}
console.log('User Registration succesful');
return done(null, newUser);
});
}
});
};
// Delay the execution of findOrCreateUser and execute the method
// in the next tick of the event loop
process.nextTick(findOrCreateUser);
})
);
// Generates hash using bCrypt
var createHash = function(password){
return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
}
}
Thanks in advance for the help!
Why don't you move the email sending logic to the passport signup strategy?

Using bcryptjs with Mongo

I am trying to encrypt user passwords using Bcrpyt for my Angular app which uses Mongodb in the backend.
Here is the code
Model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
bcrypt = require('bcryptjs'),
SALT_WORK_FACTOR = 10;
var UserSchema = new mongoose.Schema({
name: String,
username: { type: String, required: true, index: { unique: true } },
email: String,
password: { type: String, required: true },
created_at: Date,
topics: [{type: Schema.Types.ObjectId, ref: 'Topic'}],
posts: [{type: Schema.Types.ObjectId, ref: 'Post'}],
comments: [{type: Schema.Types.ObjectId, ref: 'Comment'}]
});
UserSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password along with our new salt
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
next();
});
});
});
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
mongoose.model('User', UserSchema);
Create & Login inside Controller
var mongoose = require('mongoose');
var User = mongoose.model('User');
module.exports = (function() {
return {
login: function(req, res) {
User.findOne({email: req.body.email}, function(err, user) {
if(user === null) {
var error = "User not found"
console.log(error);
}
else{
user.comparePassword(req.body.password, function(err, isMatch){
if(err){
console.log("Password dont match");
} else{
console.log(user)
res.json(user);
}
})
}
})
},
create: function(req, res) {
var user = new User({name: req.body.name, username:req.body.username, email:req.body.email, password:req.body.password, created_at: req.body.created_at});
user.save(function(err) {
if(err) {
console.log('something went wrong');
} else {
console.log('successfully added a user!');
res.redirect('/');
}
})
}
})();
The user create function is working fine, saving in the passwords encrypted. But during Login it is not properly comparing the encrypted password against the input. Lets user through regardless of any password.
Also how would I go about showing errors incase of user not found and also for password not matching(this is a secondary Q.
Primary concerned about even wrong password being accepted.
Thanks for the Help.
You are checking if there is any error during password matching but not checking if the input password matches the hashed one.
user.comparePassword(req.body.password, function(err, isMatch){
if(err){
return console.log("Password dont match");
}
if (isMatch) {
// password matches. Log the user in
}
});

Passport LocaStrategy signup redirect issue

I am trying to redirect user after signup on Passport
passport.use('local-signup', new LocalStrategy({
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true},
function(req, username, password, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose username is the same as the forms username
User.findOne({ username : username }, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('signupMessage', 'Username is already taken.'));
} else {
var newUser = new User();
newUser.username = username;
newUser.password = password;
newUser.email = req.body.email;
newUser.save(function(err) { // create user
if (err) {
return next(err);
} else {
var site = new Site({user : newUser.username, siteTitle: newUser.username});
site.save(function(err) { // create website
if (err) {
return next(err);
} else {
newUser.sites.push(site); // push site'id in sites field in user
newUser.save(); // save user after site'id has been push
};
});
};
res.redirect('users/' + username);
});
...
and I get ReferenceError: res is not defined
Any help would be appreciated as I am starting Mean dev.
Thanks a lot
You shouldn't be handling redirects in here - the function of the strategy is to determine whether the user is authenticated or not.
Redirects can then be done in the middleware:
app.post('/login', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login'
}));
http://passportjs.org/guide/authenticate/

how can i unit test passportjs with mocha?

i would like to test this method using mocha and i don't know where to start ?
the route :
app.post('/signup', passport.authenticate('local-signup', {
failureRedirect: '/#/',
failureFlash: true
}),
function(req, res) {
res.jsonp(req.user);
});
and here is the definition of my service :
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'email',
passwordField: 'password',
pseudoField: 'pseudo',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, pseudo, done) {
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({
'local.email': email
}, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
console.log('That email is already taken');
//var newUser = new User();
return done(404, null);
// return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
// if there is no user with that email
// create the user
console.log('creation new user');
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
newUser.local.pseudo = pseudo;
console.log(pseudo);
console.log(newUser.local);
// save the user
console.log('going to save in bdd');
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
i just need some help to know how i could test this methode and how to call it.

Categories