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.
Related
I made a login with bcrypt.
I also made a page where users can edit their information, like their bio etc.
Each time an user edit his bio on this page the hash from bcrypt change, which is normal i suppose, but the user login back, the password is wrong...
I used the same model for mongoDb for the user when he log in and when he edit his data.
I started node.js recently so I apologize if my question is stupid,,,
The controller code with the Post :
app.post('/settings-user', mid.requiresLogin, function(req, res, next){
User.findById(req.session.userId, function (err, user) {
// todo: don't forget to handle err
if (!user) {
return res.redirect('/edit');
}
// good idea to trim
var bio = req.body.bio.trim();
// validate
if (!bio) { // simplified: '' is a falsey
req.flash('error', 'One or more fields are empty');
return res.redirect('/settings-user'); // modified
}
// no need for else since you are returning early ^
user.bio = bio;
// don't forget to save!
user.save(function (err) {
// todo: don't forget to handle err
res.redirect('/settings-user/');
});
});
});
The User model :
app.post('/settings-user', mid.requiresLogin, function(req, res, next){
User.findById(req.session.userId, function (err, user) {
// todo: don't forget to handle err
if (!user) {
return res.redirect('/edit');
}
// good idea to trim
var bio = req.body.bio.trim();
// validate
if (!bio) { // simplified: '' is a falsey
req.flash('error', 'One or more fields are empty');
return res.redirect('/settings-user'); // modified
}
// no need for else since you are returning early ^
user.bio = bio;
// don't forget to save!
user.save(function (err) {
// todo: don't forget to handle err
res.redirect('/settings-user/');
});
});
});
The User model :
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
trim: true
},
name: {
type: String,
required: true,
trim: true
},
password: {
type: String,
required: true
},
bio: {
type: String
}
});
// authenticate input against database documents
UserSchema.statics.authenticate = function(email, password, callback) {
User.findOne({ email: email })
.exec(function (error, user) {
if (error) {
return callback(error);
} else if ( !user ) {
var err = new Error('User not found.');
err.status = 401;
return callback(err);
}
bcrypt.compare(password, user.password , function(error, result) {
if (result === true) {
return callback(null, user);
} else {
return callback();
}
})
});
}
// hash password before saving to database
UserSchema.pre('save', function(next) {
var user = this;
bcrypt.hash(user.password, 10, function(err, hash) {
if (err) {
return next(err);
}
user.password = hash;
next();
})
});
var User = mongoose.model('User', UserSchema);
module.exports = User;
the pug file :
div
form(method='post', action='/settings-user')
label ADD BIO
br
input(type='text', name='bio', placeholder='Enter something', required='')
input(type='submit', value='Add Bio')
</body>
If anyone could help,,,
thank you!
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?
Having an awful time trying to compare passwords using bcryptjs so I can sign a JWT but trying to login I can't compare to sign the token and send to the client.
Problem
I can hash a password and store into the DB, where I'm having issues is using the .compare() method and passing in the hash parameter. I'm not quite sure what to pass in as the hash value.
Technology:
NodeJS: 5.4.1
bcryptjs: 2.3.0
express: 4.14.0
body-parser: 1.15.2
MongoDB: 3.2.5
mongoose: 4.6.1
user.routes.js
var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var bcrypt = require('bcryptjs');
var salt = bcrypt.genSaltSync(10);
var config = require('../config/database');
User = require('../models/user.model.js');
// Create new User
router.post('/', function(req, res){
var user = req.body;
if(!req.body.email || !req.body.password){
res.json({success: false, message: 'Please pass email and password'});
} else {
User.addUser(user, function(err, user){
if(err){
res.send(err);
}
bcrypt.genSalt(10, function(err, salt){
bcrypt.hash(user.password, salt, function(err,hash){
user.password = hash;
user.save();
console.log('new user', user);
res.json({success: true, message: 'Create user successful'});
})
})
});
}
});
Getting errors during password compare:
// Authenticate a User
//email: test#test.com
//password: password
router.post('/login', function(req, res){
User.findOne({ email: req.body.email }, function (err, user){
if (err){
res.send(err);
}
if(!user){
res.json({ success: false, message: 'Authentication failed. User not found'});
} else if (user) {
// where does this hash value get defined and passed in?
bcrypt.compare(req.body.password, hash, function(err, res){
if(user.password != req.body.password){
console.log('password incorrect');
//res.json({ success: false, message: 'Authentication failed. Password incorrect'});
} else {
var token = jwt.sign({
email: user.email
}, config.secret, {
expiresIn: 60 // expressed in seconds
});
console.log('token contents', token);
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
});
}
});
});
The hash value that you have to pass to the compare method is the one you got when you called bcrypt.hash method. I suppose you saved that hash associated to the user in some DB, so you have to get that hash and pass it to compare method as second parameter.
I think you are doing wrong the comparison in the callback of the compare method. You shouldn't compare passwords, the compare method does that for you. You just have to check if res is true or false. If it is true, then passwords are the same, other case they are different.
If you have more doubts about the implementation in this article you have a very simple example about that:
https://solidgeargroup.com/password-nodejs-mongodb-bcrypt?lang=es
It is written with promises, but it's very easy to understand.
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
}
});
I have read all the related questions and responses and still can't fix this issue. Please see the code below and help me understand why terminal is throwing 'undefined is not a function'.
For a rundown of the functions:
The query section looks up SQL gets the users PW from DB. Parse results gets just the pw and eliminates the 'key' from the key value pair. Move pw function is there just as a buffer so that compare PW will not execute until we have retrieved the pw to compare with.
I have been stuck on this for a while, any help is much appreciated. To see the running app, go here...a working un/pw combo are user5 1234, but bc of the error it will look up username, password, verify that its a match (the compare pw and the look up pw functions actually do work and tell you if its a existing pw and un combo, but when i try and return done(user, null) to the passport login route, it crashes...
https://[redacted].com/
var express = require('express');
var router = express.Router();
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var db = require('../database');
var returnedPw;
var flash = require('connect-flash');
var session = require('express-session');
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 's',
user : 'n',
password : '',
database : 's'
});
//stripped credentioals
// Include User Model
var User = require('../models/user');
// Include Student Model
var Client = require('../models/client');
// Include Instructor Model
var Company = require('../models/company');
router.get('/signup', function(req, res, next) {
res.render('users/signup');
});
router.post('/signup', function(req, res, next){
// Get Form Values
console.log('starting post and making new user');
var first_name = req.body.first_name;
var last_name = req.body.last_name;
var street_address = req.body.street_address;
var city = req.body.city;
var state = req.body.state;
var zip = req.body.zip;
var email = req.body.email;
var username = req.body.username;
var password = req.body.password;
var password2 = req.body.password2;
var type = req.body.type;
// Form Field Validation
req.checkBody('first_name', 'First name field is required').notEmpty();
req.checkBody('last_name', 'Last name field is required').notEmpty();
req.checkBody('email', 'Email field is required').notEmpty();
req.checkBody('email', 'Email must be a valid email address').isEmail();
req.checkBody('username', 'Username field is required').notEmpty();
req.checkBody('password', 'Password field is required').notEmpty();
req.checkBody('password2', 'Passwords do not match').equals(req.body.password);
var errors = req.validationErrors();
if(errors){
res.render('users/signup', {
errors: errors,
first_name: first_name,
last_name: last_name,
street_address: street_address,
city: city,
state: state,
zip: zip,
email: email,
username: username,
password: password,
password2: password2
});
} else {
var newUser = new User({
email: email,
username:username,
password: password,
type: type
});
console.log('calling post to database file to receive new user:' + newUser)
// THIS IS WHERE WE ARE POSTING THE NEW USER TO THE DATABASE!!!
db.postUsers(newUser);
var newClient = new Client({
first_name: first_name,
last_name: last_name,
address: [{
street_address: street_address,
city: city,
state: state,
zip: zip
}],
email: email,
username:username
});
if(type == 'client'){
//User.saveClient(newUser, newClient, function(err, user){
// console.log('Client created');
///}); works but replacing w sql
} else {
var newCompany = new Company({
first_name: first_name,
last_name: last_name,
address: [{
street_address: street_address,
city: city,
state: state,
zip: zip
}],
email: email,
username:username
});
//works but replacing w sql
//User.saveCompany(newUser, newCompany, function(err, user){
// console.log('Company created');
//});
//sql save function
console.log('calling sql save..');
//db.postUsers(newUser, newClient);
}
req.flash('success','User added');
res.redirect('/');
}
});
<!--//// -USER AUTH SECTION- \\\\--!><!--//// -USER AUTH SECTION- \\\\--!><!--//// -USER AUTH SECTION- \\\\--!>
/*
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.getUserById(id, function (err, user) {
done(err, user);
});
}); */
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
router.post('/login',passport.authenticate('local',{failureRedirect:'/', failureFlash:'Wrong Username or Password'}), function(req, res){
req.flash('success','You are now logged in');
var usertype = req.user.type;
res.redirect('/'/*+usertype+'s/classes' */);
});
passport.use(new LocalStrategy(
function(username, password, done ) {
console.log('in users the username is ' + username)
connection.query('SELECT password FROM t_user WHERE username = ?', username, function(err, user) {
parseResults(user, done);
});// end query
function parseResults(user, done) {
Object.keys(user)[0];
var key = Object.keys(user)[0];
user[key];
var storedPw = user[key];
for(var i in storedPw){
returnedPw = storedPw[i];
}
console.log('returnedPw is defined here ' +returnedPw);
movePw(returnedPw, done);
}// end function
var candidatePassword = password;
function movePw (returnedPw, done) {
if (returnedPw ) {
User.comparePassword(candidatePassword, returnedPw, function(err, isMatch) {
if (err) return done(err);
if(isMatch) {
//return done(null, user);
// req.flash('success','User Access Granted');
//console.log('go head')
user = username;
return done(null, user);
//done(null, user);
//notifyOuterScope();
//return true;
} else {
console.log('Invalid Password');
// Success Message
req.flash('failureFlash','User Access Denied. False Password');
return done(null, false, { message: 'Invalid password' });
}
});
}
else {console.log('return PW not defined')}
}
}// end outer function ?
));//end passport
// Log User Out
router.get('/logout', function(req, res){
req.logout();
// Success Message
req.flash('success', "You have logged out");
res.redirect('/');
});
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/')
}
/* COMPARING PASSWORDS */
/* where are we returning the password from the user db profile?
bcrypt.hash('mypassword', 10, function(err, hash) {
if (err) { throw (err); }
bcrypt.compare('mypassword', hash, function(err, result) {
if (err) { throw (err); }
console.log(result);
});
});
*/
module.exports = router;
The code is a working example of using SQL and passport.js with node. I was having difficulty using the Local Strategy required for passport (using sql commands instead of mongodb commands that you see in most passport documentation), and it turns out the reason is because I wasn't passing the correct variables/ was also passing in unnecessary variables.
I corrected it above. Instead of using the User.FindOne Mongo db query in local strategy, this is an example of how to do the same username/ password querys using SQL, within passport local strategy. There isn't much documentation on using SQL and passport / node.