Is Not A Function Trouble Logging In With Passport - javascript

Hey I'm new to express and am trying to login as a user. With the following code, I get the error below. I thought since I did module.exports.comparePassword it would work. Is there something I'm missing? Thanks for any help
events.js:160
throw er; // Unhandled 'error' event
^
TypeError: user.comparePassword is not a function
at /Users/Sam/Desktop/teach/routes/users.js:112:17
at Query. (/Users/Sam/Desktop/teach/node_modules/mongoose/lib/model.js:3343:16)
at /Users/Sam/Desktop/teach/node_modules/kareem/index.js:259:21
at /Users/Sam/Desktop/teach/node_modules/kareem/index.js:127:16
at _combinedTickCallback (internal/process/next_tick.js:67:7)
at process._tickCallback (internal/process/next_tick.js:98:9)
models/user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcryptjs')
var userSchema = new Schema({
email: { type: String },
password: { type: String },
type: { type: String }
});
var User = mongoose.model('User', userSchema);
module.exports = User;
//Get a User by id
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
//Get a User by email
module.exports.getUserByEmail = function(email, callback){
var query = {email : email}
User.findOne(query, callback);
}
//Save a student
module.exports.saveStudent = function(newUser, newStudent, callback){
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash){
if (err) {throw err}
newUser.password = hash;
//Saves both a user and student
async.parallel([newUser.save, newStudent.save], callback);
})
})
}
//Save a instructor
module.exports.saveInstructor = function(newUser, newInstructor, callback){
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash){
if (err) {throw err}
newUser.password = hash;
//Saves both a user and instructor
async.parallel([newUser.save, newInstructor.save], callback);
})
})
}
//Checks if password matches.
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch){
if(err) throw err;
callback(null, isMatch);
});
}
routes/users.js
var express = require('express');
var router = express.Router();
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var User = require('../models/user');
var Student = require('../models/student');
var Instructor= require('../models/instructor');
router.get('/signup', function(req, res, next) {
res.render('users/signup');
});
router.get('/login', function(req, res, next){
res.render('users/login')
});
//Registering a user
router.post('/signup', function(req, res, next){
var first_name = req.body.first_name;
var last_name = req.body.last_name;
var email = req.body.email;
var password = req.body.password;
var password2 = req.body.password2;
var type = req.body.type;
req.checkBody('first_name', 'First name is required.').notEmpty();
req.checkBody('first_name', 'Please enter a shorter first name.').len(1, 40);
req.checkBody('last_name', 'Last name is required.').notEmpty();
req.checkBody('last_name', 'Please enter a shorter last name.').len(1, 40);
req.checkBody('email', 'Email is required.').notEmpty();
req.checkBody('email', 'Email must be valid.').isEmail();
req.checkBody('email', 'Please enter a shorter email.').len(1, 40);
req.checkBody('password', 'Password is required.').notEmpty();
req.checkBody('password2', 'Passwords must match.').equals(req.body.password);
req.checkBody('password', 'Please choose a password between 6 to 50 characters.').len(6, 50);
var errors = req.validationErrors();
if(errors){
res.render('users/signup', {
errors: errors,
first_name: first_name,
last_name: last_name,
email: email,
password: password,
password2: password2
});
} else {
var newUser = new User({
email: email,
password: password,
type: type
});
var newStudent = new Student({
first_name: first_name,
last_name: last_name,
email: email,
});
var newInstructor = new Instructor({
first_name: first_name,
last_name: last_name,
email: email,
});
if(type == 'student'){
User.saveStudent(newUser, newStudent, function(err, user){
console.log('Student saved');
});
} else {
User.saveInstructor(newUser, newInstructor, function(err, user){
console.log('Instructor saved');
});
}
res.redirect('/classes');
}
});
passport.serializeUser(function(user, done){
done(null, user._id);
});
passport.deserializeUser(function(id, done){
User.getUserByEmail(function(err, user){
done(err, user);
});
});
//Login in a user
router.post('/login',passport.authenticate('local', {
failureRedirect:'/users/login',
failureFlash:'Wrong Username or Password'
}), function(req, res){
var usertype = req.user.type;
res.redirect('/classes');
});
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, done) {
User.getUserByEmail(email, function(err, user){
if (err) return done(err);
if(!user){
return done(null, false, { message: 'Unregistered email'});
}
if (!user.comparePassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
module.exports = router;

Clearly, it will give an undefined function error since comparePassword is not a function defined for User Schema. You can use it as User.comparePassword since it has the function (in the JS file), but the user (object of the user schema - mongo) has no such function defined.
Do this before you export the User,
userSchema.methods.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch){
if(err) throw err;
callback(null, isMatch);
});
};
Hope it helps.

You have 'User' but you are using 'user'. Note the case sensitivity.
Use below code:
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, done) {
User.getUserByEmail(email, function(err, user){
if (err) return done(err);
if(!user){
return done(null, false, { message: 'Unregistered email'});
}
if (!User.comparePassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
Suggestion:
If you are using module.exports than you can export the entire block with it so you need not to write module.exports again and again like you have did.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcryptjs')
var userSchema = new Schema({
email: { type: String },
password: { type: String },
type: { type: String }
});
var User = mongoose.model('User', userSchema);
module.exports = model;
//Get a User by id
model.getUserById = function(id, callback){
User.findById(id, callback);
}
//Get a User by email
model.getUserByEmail = function(email, callback){
var query = {email : email}
User.findOne(query, callback);
}
//Save a student
model.saveStudent = function(newUser, newStudent, callback){
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash){
if (err) {throw err}
newUser.password = hash;
//Saves both a user and student
async.parallel([newUser.save, newStudent.save], callback);
})
})
}
//Save a instructor
model.saveInstructor = function(newUser, newInstructor, callback){
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash){
if (err) {throw err}
newUser.password = hash;
//Saves both a user and instructor
async.parallel([newUser.save, newInstructor.save], callback);
})
})
}
//Checks if password matches.
model.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch){
if(err) throw err;
callback(null, isMatch);
});
}
model.schema = User;

Related

bcryptjs compare function returns false when passwords contain numbers

I have used bcryptjs to hash my passwords and the registration of the user takes place fine.
But when I try to login, the bcrypt.compare function returns a false promise if the passwords contain numbers or other characters.
Everything works fine if the password is just letters.
I have searched everywhere but could not find a solution for this error?
Here are my files :
users.js (MODEL)
var mongoose = require("mongoose");
var bcrypt = require("bcryptjs");
var userSchema = mongoose.Schema({
name: String,
email: String,
password: String,
products: [{
type: mongoose.Schema.Types.ObjectID,
ref: "Product"
}],
username: String
});
var User = module.exports = mongoose.model("User", userSchema);
module.exports.createUser = function(newUser, callback) {
bcrypt.genSalt(5,function(err,salt){
bcrypt.hash(newUser.password,salt,function(err,hash){
newUser.password = hash;
newUser.save(callback);
});
});
}
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
console.log(isMatch);
callback(null, isMatch);
});
}
module.exports.getUserByUsername = function(username, callback){
var query = {username: username};
User.findOne(query, callback);
}
app.js
app.use(passport.initialize());
app.use(passport.session());
var LocalStrategy = require("passport-local").Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({username: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) {
console.log("ERR1");
return done(err);
}
if(isMatch) {
console.log("MATCH");
return done(null,user);
} else {
console.log("NOT VALID");
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);
})
})
users.js (ROUTE)
router.post("/login", passport.authenticate('local', function (error, user, info){
if(error) {
console.error(error);
console.log('Failed login:');
}
if (user === false) {
console.log("user not found");
} else {
// handle successful login ...
console.log("logged in");
}
}), function (req, res) {
res.send(req.user);
});
router.post("/signup", function (req, res) {
console.log(req.body);
var password = req.body.password;
var password2 = req.body.password2;
if(password == password2) {
var newUser = new User ({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: req.body.username
});
User.createUser(newUser, function(err, user){
if(err)
throw err;
console.log(user);
res.send(user).end();
});
} else {
res.status(500).send("{errors: \"Passwords don't match\"}").end()
}
});
Whenever I enter a password that contains numbers, I get
false
NOT VALID
user not found
I'm sorry if I have done or not done something extremely simple. This is my first time using bcryptjs.
All answers are appreciated!
In the users.js file at the comparePassword function inside the compare method of bcrypt you are throwing the error instead of passing it to the callback. Maybe if you make this change:
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
console.log(isMatch);
callback(err, isMatch);
});
}
You will see what kind of error it's generated by the case when you introduce numbers in your passwords.

How to handle Express Sessions?

I'm trying to deal with authentication sessions in Node.js, Express, Passport app.
I made lines of code to use express-session and it still can't auth even when I register new user.
Here is strategy.
// Local Strategy
passport.use(new LocalStrategy({ usernameField: 'email' }, function(username, password, done){
User.findOne({ 'email': username }, function(err, user){
if(err) throw err;
if(!user){
return done(null, false, {type: "danger", message: 'No user found'});
}
// Match Password
bcrypt.compare(password, user.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, user);
} else {
return done(null, false, {type: "danger", message: 'Wrong password'});
}
});
});
}));
Here are serializers.
passport.serializeUser(function(user, done) {
console.log(user.id);
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
console.log(user.id);
done(err, user);
});
});
Here is login and register route.
// Login page and form
router.get('/login', function(req, res) {
res.render('login');
});
router.post('/login', passport.authenticate('local',
{successRedirect: '/chat',
failureRedirect: '/login'}));
// Register page and form
router.get('/register', function(req, res) {
let errors = [];
res.render('register', { 'errors': '' });
});
router.post('/register', [
check('name').notEmpty().withMessage('Name field is empty'),
check('surname').notEmpty().withMessage('Surname field is empty'),
check('email').notEmpty().withMessage('E-mail is empty'),
check('password').notEmpty().withMessage('Password field is empty'),
check('password_confirm').notEmpty().withMessage('Password confirmation field is empty'),
check("password", "Passwords don't match")
.custom((value,{req}) => {
if (value !== req.body.password_confirm) {
throw new Error("Passwords don't match");
} else {
return value;
}
}),
], function(req, res) {
const { name, surname, email, password } = req.body;
let errors = validationResult(req);
console.log(errors.errors);
if(!errors){
res.render('register', { 'errors': errors.errors });
console.log('ebebe');
} else {
console.log('oooo');
let NewUser = new User ({
name, surname, email, password
});
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) {
NewUser.password = hash;
NewUser.save();
});
});
res.redirect('/chat');
}
});
Here is protected route.
router.get('/chat', (req, res) => {
if(req.isAuthenticated()) {
res.send('definitely secure page');
console.log(req.user);
console.log(req.isAuthenticated());
} else {
res.send('ebebe');
console.log(req.user);
console.log(req.isAuthenticated());
}
});
How to make it work properly and what am I doing wrong?
Here is a way to do it. You can use something like jsonwebtoken in combination with express-session and write a middleware function to check if the token is valid and use it to protect the routes that you want to protect. Here are some snippets of code that I hope will help guide you in the right direction.
First you can write a function like this in your UserSchema so you can use it later to generate a jwt token when the user logs in
var jwt = require('jsonwebtoken');
UserSchema.methods.generateJWT = function() {
var today = new Date();
var exp = new Date(today);
exp.setDate(today.getDate() + 60);
return jwt.sign({
id: this._id,
username: this.username,
exp: parseInt(exp.getTime() / 1000),
}, secret);
};
then in the login route you can use it to generate a token.
router.post('/login', passport.authenticate('local',
failureRedirect: '/login'}), function(req, res) {
req.user.token = user.generateJWT();
req.session.token = req.user.token;
res.redirect('/dashboard')
});
and then you can write the middleware
function auth(req, res, next) {
//console.log(req.session.token)
if (req.session.token) {
const token = req.session.token
let decoded = jwt.verify(token, secret)
console.log(decoded)
User.findById(decoded.id, function(err, user) {
if (err || !user) {
return res.redirect('/')
}
//console.log(user)
res.locals.user = user
req.user = user
next()
})
} else {
return res.redirect('/')
}
}
and then you can protect your routes with it
router.get('/protected', auth, function(req, res) {
})

Bcrypt not encrypting the password in Node JS

I am trying to make a user account system in node and am using bcrypt for hashing passwords. My syntax seems to be correct, and there is no error thrown in the console, however still the passwords aren't getting encrypted when stored in the database(mongo db).
This is my user.js file in models:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
mongoose.connect('mongodb://localhost/nodeauth');
var db = mongoose.connection;
// User
var UserSchema = mongoose.Schema({
username: {
type: String,
index: true
},
password: {
type: String,
required: true,
bcrypt: true
},
email: {
type: String
},
name: {
type: String
},
profileImage: {
type: String
}
});
var User = module.exports = mongoose.model('User', UserSchema);
module.exports.comparePassword = function(candidatePassword, hash, callback) {
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) return callback(err);
callback(null, isMatch);
});
}
module.exports.getUserById = function(id, callback) {
User.findById(id, callback);
}
module.exports.getUserByUsername = function(username, callback) {
var query = {username: username};
User.findOne(query, function(err, user) {
callback(err, user);
});
}
module.exports.createUser = function(newUser, callback) {
bcrypt.hash(newUser.password, 10, function(err, hash) {
if(err) throw err;
// Set hashed password
newUser.password = hash;
// Create user
newUser.save(callback);
});
}
And this is the relevant part of users.js file in routes which is used in the login section to check whether the username and password match(which is also not working):
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.getUserById(id, function(err, user) {
done(err, user);
});
});
passport.use(new LocalStrategy(function(username, password, done) {
User.getUserByUsername(username, function(err, user) {
if(err) throw err;
if(!user) {
console.log('Unknown 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 {
console.log('Invalid Password');
return done(null, false, {message: 'Invalid Password'});
}
});
});
}));
router.post('/login', passport.authenticate('local', {successRedirect: '/', failureRedirect:'/users/login', failureFlash:'Invalid username or password'}), function(req, res) {
console.log('Authentication Successful');
req.flash('success', 'You are logged in');
res.redirect('/');
});
module.exports = router;
I am not able to understand why the passwords aren't getting encrypted, and even then, why is comparePassword always returning a failure, thus making authentication fail every time as well.
Is there anything I am missing?

Node.js Passport strategy login with either Email or Username

I am using Passport on my node.js app and I am currently using an Username to login.
On my user register page, I have allow the user to register their unique username and email.
I want a login page with "Sign in Using username/email:" ________
Where the script can detect if there is a "#" in the field and look up the email instead of username.
I have tried for a few hours with no avail.
here is my passport.js
var mongoose = require('mongoose')
var LocalStrategy = require('passport-local').Strategy
var User = mongoose.model('User');
module.exports = function(passport, config){
passport.serializeUser(function(user, done){
done(null, user.id);
})
passport.deserializeUser(function(id, done) {
User.findOne({ _id: id }, function (err, user) {
done(err, user);
});
});
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, function(username, password, done) {
User.isValidUserPassword(username, password, done);
}));
}
EDIT: below is the user.js as requested
var mongoose = require('mongoose');
var hash = require('../util/hash.js');
UserSchema = mongoose.Schema({
username: String,
email: String,
salt: String,
hash: String
})
UserSchema.statics.signup = function(username, email, password, done){
var User = this;
hash(password, function(err, salt, hash){
if(err) throw err;
// if (err) return done(err);
User.create({
username : username,
email: email,
salt : salt,
hash : hash
}, function(err, user){
if(err) throw err;
// if (err) return done(err);
done(null, user);
});
});
}
UserSchema.statics.isValidUserPassword = function(username, password, done) {
this.findOne({username : username}, function(err, user){
// if(err) throw err;
if(err) return done(err);
if(!user) return done(null, false, { message : 'Incorrect username.' });
hash(password, user.salt, function(err, hash){
if(err) return done(err);
if(hash == user.hash) return done(null, user);
done(null, false, {
message : 'Incorrect password'
});
});
});
};
var User = mongoose.model("User", UserSchema);
module.exports = User;
Ok, you should have something like this in your Mongoose model:
UserSchema.statics.isValidUserPassword = function(username, password, done) {
var criteria = (username.indexOf('#') === -1) ? {username: username} : {email: username};
this.findOne(criteria, function(err, user){
// All the same...
});
};
its a mongoose thing rather than a passport thing, and if you can try out this, using bredikhin's answer ^_^ :
var criteria = {$or: [{username: username}, {email: username}, {mobile: username}, {anything: username}]};
The key is to find a user through a query from mongodb!!!
In this way, you can have whatever query you want to find a user.
For sign ins with username OR email, you could also use passport-local-mongoose's findByUsername option modify the queryParameters
// add passport functions
// schema.plugin(passportLocalMongoose);
schema.plugin(passportLocalMongoose, {
// allow sign in with username OR email
findByUsername: function(model, queryParameters) {
// start
// // queryParameters => { '$or' : [ { 'username' : 'searchString' } ] }
// iterate through queryParameters
for( let param of queryParameters.$or ){
// if there is a username
if( typeof param == "object" && param.hasOwnProperty("username") ){
// add it as an email parameter
queryParameters.$or.push( { email : param.username } );
}
}
// expected outcome
// queryParameters => { '$or' : [ { 'username' : 'searchString' }, { 'email' : 'searchString' } ] }
return model.findOne(queryParameters);
}
});
its very simple you have to redefine the passport strategy your self, just like in the code below,
serialize with username
passport.serializeUser(function(user, done) {
done(null, user.username);});
deserialize with username
passport.deserializeUser(function(username, done) {
User.findOne({username:username},function(err, user){
done(err,user);
}); });
Passport Strategy
//passport strategy
passport.use(new LocalStrategy(function(username, password, done) {
console.log(username.includes("#"));
User.findOne((username.includes("#"))?{email:username}:{username:username}, function(err, user) {
if (err) {return done(err); }
if (!user) {console.log("i am ERROR"); return done(null, false, { message: 'Incorrect username.' });}
if (user.password===password) {return done(null, user); }
return done(null, false);
});
}
));
NOTE: Here user.password===password means that the password in database is stored as plaintext.. And you have to insert password manually to database such as password : req.body.password also you have to apply encryption and decryption user self before adding or in comparing.
Or you just make a middle-ware to handle the situation like so:
const User = require("../models/user");
var middlewareObj = {};
middlewareObj.checkUsername = function (req, res, next) {
if (req.body.username.indexOf('#') !== -1) {
User.findOne({ email: req.body.username }, (err, foundUser) => {
if (err || !foundUser) {
req.flash('error', 'Please check your username or password');
return res.redirect('/login');
} else {
req.body.username = foundUser.username;
next();
}
});
} else {
next();
}
}
module.exports = middlewareObj;
And simply add it to the login route:
app.post('/login', middleware.checkUsername, function (req, res, next) {
//Login logic goes here
}

How to Check Current Password And (If Correct) Update Password Using Passport.js Local Strategy

I need to write commonplace code to check a Current User Password value submitted via form to see if it matches the existing password in the database, and if so, update the password to a New Password value that was submitted via the same form.
Yet, I can't find any good examples of how to do this using Passport.js. Can anyone advise as to how I can do this in my Users controller down below, if there are any helper functions provided by passport that I should use for this, and how I do this with hashed and salted passwords?
Here is my code:
// Form Submitted
req.body = {
_id: '5294198b7b35ad2794000001',
email: 'testusera1#abc.net',
name: 'John Smith',
provider: 'local',
username: 'ab123',
current_password: 'currentpassword',
new_password: 'newpassword'
}
// Route
app.put('/users/me', users.update);
// Controller
var mongoose = require('mongoose'),
User = mongoose.model('User'),
_ = require('underscore'),
passport = require('passport'),
LocalStrategy = require('passport-local').Strategy;
exports.update = function(req, res) {
var user = req.user
user = _.extend(user, req.body);
user.save(function(err) {
if(err) { console.log(err) };
res.jsonp(user);
});
};
// Passport Config File
module.exports = function(passport) {
//Serialize sessions
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findOne({
_id: id
}, function(err, user) {
done(err, user);
});
});
//Use local strategy
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
function(email, password, done) {
User.findOne({
email: email
}, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Unknown user'
});
}
if (!user.authenticate(password)) {
return done(null, false, {
message: 'Invalid password'
});
}
return done(null, user);
});
}
));
};
hashed and salted password
full example on github
// Bcrypt middleware
userSchema.pre('save', function(next) {
var user = this;
if(!user.isModified('password')) return next();
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if(err) return next(err);
bcrypt.hash(user.password, salt, function(err, hash) {
if(err) return next(err);
user.password = hash;
next();
});
});
});
// Password verification
userSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if(err) return cb(err);
cb(null, isMatch);
});
};

Categories

Resources