I have some problem with passport and sequelize on my Node.js project.
In fact, when I want to signup, I'm using postman to call the /users/signup route and it show me : Unexpected '' and :
::1 - - [07/Aug/2018:09:20:19 +0000] "POST /users/signup HTTP/1.1" - - "-" "PostmanRuntime/7.2.0"
Executing (default): SELECT "user_id", "username", "name", "firstname", "email", "type", "location", "password", "createdAt", "updatedAt" FROM "users" AS "users" LIMIT 1;
There is the code :
/* CREATE an account */
app.post('/users/signup', (req, res) => {
db.users.find({ $or: [{ email: req.body.email }, { username: req.body.username }] }).then(user => {
if (err) {
return res.send(err);
}
if (user) {
if (user.email == req.body.email) {
return res.send("This email is already taken.")
}
return res.send("This username is already taken.")
}
else {
const data = {
username: req.body.username,
name: req.body.name,
firstname: req.body.firstname,
email: req.body.email,
location: req.body.location,
type: req.body.type,
password: req.body.password
};
db.users.create({
username: data.username,
name: data.name,
firstname: data.firstname,
email: data.email,
location: data.location,
type: data.type,
password: data.password
}).then(newUser => {
res.send("newUser saved to database")
// `req.user` contains the authenticated user.
//TODO : res.redirect('/profile/' + req.body.username);
})
.catch(err => {
console.log(err);
res.status(400).send("unable to save this newUser to database");
})
}
}).catch(err => {
console.log(err);
res.status(400).send("signup failed");
})
})
And my model (db.users) :
const bcrypt = require("bcrypt-nodejs");
module.exports = (sequelize, DataTypes) => {
// TABLE USERS
const Users = sequelize.define('users', {
user_id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
firstname: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
type: {
type: DataTypes.STRING,
allowNull: false,
},
location: {
type: DataTypes.STRING
},
password: {
type: DataTypes.STRING,
allowNull: false
}
});
// methods ======================
// generating a hash
Users.generateHash = function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// checking if password is valid
Users.validPassword = function (password) {
return bcrypt.compareSync(password, this.password);
};
//hashing a password before saving it to the database
Users.beforeCreate('save', function (next) {
var user = this;
bcrypt.hash(user.password, 10, function (err, hash) {
if (err) {
return next(err);
}
user.password = hash;
next();
})
});
return Users;
};
My passport.js :
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
var db = require('../db')
// expose this function to our app using module.exports
module.exports = function (passport) {
var User = db.users;
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function (user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function (id, done) {
User.find({
where: { user_id: user_id }
})
.then(function (user) {
done(err, user);
}).catch(function (err) {
return done(err);
})
});
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function (req, email, password, done) { // callback with email and password from our form
// 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({ email : email }, function (err, user) {
// if there are any errors, return the error before anything else
if (err)
return done(err);
// if no user is found, return the message
if (!user)
return done(null, false, { message: 'User not found.' }); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong
if (!user.validPassword(password))
return done(null, false, { message: 'Incorrect password.' }); // create the loginMessage and save it to session as flashdata
// all is well, return successful user
return done(null, user);
});
}));
};
I can't find the source of the problem. I'm new with Node.js and I'm stuck since few hours. Is there someone here who can help me please?
After discussion, we found out that the first error comes from the beforeSave hook on the User db model, where a hash function was used missing a null parameter as third parameter, and thus bcrypt was throwing a "no callback passed" error. There is several other things going wrong, such as missuses of promises and callbacks, and I suggest going through a learning of promises, and double checking documentation and examples on how to use the libraries, such as Sequelize and bcrypt (how to generate and use a salt for example).
Previous answer
I think the problem comes from your 'local-login' strategy : passport uses callbacks, but sequelize uses promises, so your User.findOne callback is never called. Try something like that :
app.post("/users/signup", (req, res) => {
return db.users
.find({
$or: [
{
email: req.body.email
},
{
username: req.body.username
}
]
})
.then(user => {
if (user) {
if (user.email == req.body.email) {
return res.send("This email is already taken.");
}
return res.send("This username is already taken.");
} else {
return db.users
.create({
username: req.body.username,
name: req.body.name,
firstname: req.body.firstname,
email: req.body.email,
location: req.body.location,
type: req.body.type,
password: req.body.password
})
.then(newUser => {
res.send("newUser saved to database");
});
}
})
.catch(err => {
console.error(err);
res.status(400).send("unable to save this newUser to database");
});
});
Same thing for the route, and the deserialize :
app.post('/users/signup', (req, res) => {
db.users.find({ $or: [{ email: req.body.email }, { username: req.body.username }] })
.then(user=>{})
.catch(err=>{})
Related
I have one model is user in that model I was added email, username, password and name , when I have insert this data using node JS with the help of rest API, so that condition all 4 records are stored in one table
but I want email and name is stored in registration table and username and password stored in login table ,when I put login request using postman it with username name and password credentials it gives the successful response.
I am new to Node
My controller is
exports.user_signup = (req, res, next) => {
User.find({ username: req.body.username })
.exec()
.then(user => {
if (user.length >= 1) {
return res.status(409).json({
message: "Mail exists"
});
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if (err) {
return res.status(500).json({
error: err
});
} else {
const user = new User({
_id: new mongoose.Types.ObjectId(),
username: req.body.username,
password: hash,
email: req.body.email,
contact: req.body.contact,
});
user
.save()
.then(result => {
// console.log(result);
res.status(201).json({
message: "User created"
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
}
});
}
});
};
My Postman post method is in JSON form
{
"username":"tene",
"password":"tene",
"email":"tene#gmail.com",
"contact":1234567890
}
You can try this:
import mongoose from 'mongoose'
const { Schema } = mongoose
const userSchema = new Schema(
{
registrationTable : {
email: { type: String, required: true },
mobileNo: { type: String, required: true }
},
loginTable: {
username: { type: String, required: true },
password: { type: String, required: true }
}
},
{ timestamps: true }
)
const UserModel = mongoose.model('User', userSchema)
It will depend on you if you wanna make registration and login table as an object or array, but this will sure help.
required: true will be for, you need that value necessary, if you dont want some value just remove this.
I am trying to update user data in the settings page. Where he/she can change all details like name, last name, birthday and so on. Here is the auth controller:
module.exports = {
async CreateUser(req, res) {
const schema = Joi.object().keys({
username: Joi.string()
.min(4)
.max(10)
.required(),
email: Joi.string()
.email()
.required(),
firstName: Joi.string()
.required(),
lastName: Joi.string()
.required(),
position: Joi.string()
.required(),
password: Joi.string()
.min(5)
.required(),
});
const { error, value } = Joi.validate(req.body, schema);
if (error && error.details) {
return res.status(HttpStatus.BAD_REQUEST).json({ msg: error.details })
}
const userEmail = await User.findOne({
email: Helpers.lowerCase(req.body.email)
});
if (userEmail) {
return res
.status(HttpStatus.CONFLICT)
.json({ message: 'Email already exist' });
}
const userName = await User.findOne({
username: Helpers.firstUpper(req.body.username)
});
if (userName) {
return res
.status(HttpStatus.CONFLICT)
.json({ message: 'Username already exist' });
}
return bcrypt.hash(value.password, 10, (err, hash) => {
if (err) {
return res
.status(HttpStatus.BAD_REQUEST)
.json({ message: 'Error hashing password' });
}
const age = moment().diff(moment([value.byear, value.bmonth - 1, value.bday]), 'years');
const body = {
username: Helpers.firstUpper(value.username),
email: Helpers.lowerCase(value.email),
firstName: value.firstName,
lastName: value.lastName,
position: value.position,
password: hash,
};
User.create(body)
.then(user => {
const token = jwt.sign({ data: user }, dbConfig.secret, {
expiresIn: '5h'
});
res.cookie('auth', token);
res
.status(HttpStatus.CREATED)
.json({ message: 'User created successfully', user, token });
})
.catch(err => {
res
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.json({ message: 'Error occured' });
});
});
},
User model
const userSchema = mongoose.Schema({
username: { type: String },
email: { type: String },
isVerified: { type: Boolean, default: false },
firstName: { type: String },
lastName: { type: String },
position: { type: String },
password: { type: String },
I guess I shoud have a route like this:
router.post('/user/settings', AuthHelper.VerifyToken, user.editUser);
How should it look like editUser controller according to above CreateUser function? I am using Angular in the front-end. But I think it doesn't matter. I assume 90 percent should be the same as CreateUser but what exactly should be changed so the user can update his/her details in settings form and change data in the model?
So you want to update some of user's fields (such as firstName, lastName and etc.), not replacing the whole information. Then you might want to get the current user's data first and then update only those allowed fields.
Please find the sample code below.
/**
* User router
*/
router.put('/user/:userId', AuthHelper.VerifyToken, user.editUser);
// This function will be triggered when Express finds matching route parameter
router.param('userId', function (req, res, next, id) {
User.findOne(id, function (err, user) {
if (err) {
next(err);
} else if (user) {
// When it finds user information, bind that to request object, which will be used in the other middlewares.
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
/**
* User controller
*/
exports.editUser = (req, res, next) => {
let { user } = req;
// You pick only allowed fields from submitted body
const allowedFields = { firstName: req.body.firstName, lastName: req.body.lastName, birthday: req.body.birthday };
// Override the current user data with new one
user = Object.assign(user, allowedFields);
user.save((err, savedUser) => {
if (err) {
return next(err);
}
res.json(savedUser.toJSON());
});
};
I am trying to build a login system with Facebook, Google and local passport, I am using Node.js, passport.js, and ORM to finish it, but I faced one problem now, my user model is like below
const User = connection.define('user', { googleID: {
type: Sequelize.STRING, }, facebookID: {
type: Sequelize.STRING, }, firstname: {
type: Sequelize.STRING,
notEmpty: true,
},
lastname: {
type: Sequelize.STRING,
notEmpty: true,
},
username: {
type: Sequelize.TEXT,
},
email: {
type: Sequelize.STRING,
validate: {
isEmail: true,
},
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
last_login: {
type: Sequelize.DATE,
}, });
After this setting, whenever I want to log on my website through Facebook, it kept showing SEQUELIZE ERROR, password required! I know it the reason is I don't have password while signing in with Facebook, but what can I do with this issue? I saw this page on GitHub https://github.com/feathersjs/authentication/issues/168
but I still have no clue in using this feathers-joi tool, can anyone give me some advice? Thank you
Here is my Facebook.js code
const passport = require('passport');
const cookieSession = require('cookie-session');
const FacebookStrategy = require('passport-facebook').Strategy;
const User = require('../models/User');
const keys = require('../secret/keys.js');
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then(user => {
done(null, user);
});
});
passport.use(new FacebookStrategy({
clientID: keys.facebookClientID,
clientSecret: keys.facebookClientSecret,
callbackURL: '/auth/facebook/callback',
}, (accessToken, refreshToken, profile, done) => {
User.findOne({ where: { facebookID: profile.id } }).then(existinguser => {
if (existinguser) {
//Nothing will happen, the ID already exists
done(null, existinguser);
}else {
User.create({ facebookID: profile.id }).then(user => done(null, user));
}
});
}));
My passport.js would be like below(Still need some fix)
var bCrypt = require('bcrypt-nodejs');
module.exports = function(passport, user) {
var User = user;
var LocalStrategy = require('passport-local').Strategy;
passport.use('local-signup', new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
var generateHash = function(password) {
return bCrypt.hashSync(password, bCrypt.genSaltSync(8), null);
};
User.findOne({
where: {
email: email
}
}).then(function(user) {
if (user)
{
return done(null, false, {
message: 'That email is already taken'
});
} else
{
var userPassword = generateHash(password);
var data =
{
email: email,
password: userPassword,
firstname: req.body.firstname,
lastname: req.body.lastname
};
User.create(data).then(function(newUser, created) {
if (!newUser) {
return done(null, false);
}
if (newUser) {
return done(null, newUser);
}
});
}
});
}
));
}
try this
passport.use(new FacebookStrategy({
clientID: keys.facebookClientID,
clientSecret: keys.facebookClientSecret,
callbackURL: '/auth/facebook/callback',
}, (accessToken, refreshToken, profile, done) => {
User.findOne({ where: { facebookID: profile.id } }).then(existinguser => {
if (existinguser) {
//Nothing will happen, the ID already exists
done(null, existinguser);
}else {
var object = {
facebookID: profile.id,
password: ""
}
User.create(object).then(user => done(null, user));
}
});
}));
Good day,
I am unable to login as bcrypt compareSync always returns false:
router.post('/authenticate', function(req, res, next){
User.findOne({username: req.body.username}, function(err, user){
//handling errors
if(!bcrypt.compareSync(req.body.password, user.password)){
return res.status(401).json({
success: false,
message: 'Invalid login credentials!'
});
}
const token = jwt.sign({user: user}, 'secret', {expiresIn: 7200});
res.status(200).json({
success: true,
message: 'Successfully logged in',
token: token,
userId: user._id
});
});
});
And here is how i define the user upon account creation:
var user = new User({
username: req.body.username,
password: bcrypt.hashSync(req.body.password, 10),
email: req.body.email
});
And the error received when trying to login:
Response {_body: "{"success":false,"message":"Invalid login credentials!"}", status: 401, ok: false, statusText: "Unauthorized", headers: Headers, …}
How I implemented bcrypt integration in the model:
import * as mongoose from "mongoose";
import * as bcrypt from "bcryptjs";
export interface IUser extends mongoose.Document {
name: string;
username: string;
password: string;
comparePassword(candidatePassword: string): Promise<boolean>;
}
export const schema = new mongoose.Schema({
name: String,
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
}, { timestamps: { createdAt: "created_at", updatedAt: "updated_at" } });
schema.pre("save", function (next) {
bcrypt.hash(this.password, 10, (err, hash) => {
this.password = hash;
next();
});
});
schema.pre("update", function (next) {
bcrypt.hash(this.password, 10, (err, hash) => {
this.password = hash;
next();
});
});
schema.methods.comparePassword = function (candidatePassword: string): Promise<boolean> {
let password = this.password;
return new Promise((resolve, reject) => {
bcrypt.compare(candidatePassword, password, (err, success) => {
if (err) return reject(err);
return resolve(success);
});
});
};
export const model = mongoose.model<IUser>("User", schema);
export const cleanCollection = () => model.remove({}).exec();
export default model;
Full example: https://jonathas.com/token-based-authentication-in-nodejs-with-passport-jwt-and-bcrypt/
I honestly don't see anything wrong with your code....
I assume you are also using mongoose based on your findOne function. I did almost exactly what you did, but I handled the hashing in my model, rather than passing a hashed value to the model. You can do it like this:
User Model
password:
set: function(v) {
return bcrypt.hashSync(v, 10);
}
I cannot guarantee this will work for you since I'm not 100% sure if you are using Mongoose. It's worth a shot though.
Good luck!
I created added some password reset functionality to my sign up flow that allows users to change their password, but when a user resets their password, they are unable to login with the new password and I receive a consoled Server Error. I'm wondering if this has to do with updating the user password outside of the passportjs logic or maybe because my password isn't hashed on the update?
Here is the console log:
GET /login 200 9.507 ms - 1793
GET /stylesheets/styles.css 304 1.093 ms - -
Database query triggered
Executing (default): SELECT `user_id`, `first_name` AS `firstName`, `last_name` AS `lastName`, `email`, `password`, `organization_id` AS `organizationId`, `reset_password_token` AS `resetPasswordToken`, `reset_password_expires` AS `resetPasswordExpires`, `createdAt`, `updatedAt` FROM `user` AS `user` WHERE `user`.`email` = 'test#gmail.com' LIMIT 1;
Server Error
POST /login 302 16.169 ms - 56
GET /login 200 10.926 ms - 1862
Here is my password update route:
var express = require('express');
var siteRoutes = express.Router();
var path = require('path');
var async = require('async');
var crypto = require('crypto');
var nodemailer = require('nodemailer');
var sgTransport = require('nodemailer-sendgrid-transport');
var moment = require('moment');
var url = require('url');
var passport = require(path.resolve(__dirname, '..', '..','./config/passport.js'));
var models = require('../models/db-index');
/*==== /RESET ====*/
siteRoutes.route('/reset/:token')
.get(function(req, res){
var urlPath = url.parse(req.url).pathname.split('/');
var urlToken = urlPath[2];
console.log(urlToken);
models.User.findOne({
where: {
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: moment().format('YYYY-MM-DD HH:mm:ss')
}
}
}).then(function(){
res.render('pages/app/reset-password.hbs',{
urlToken: urlToken
});
})
})
.post(function(req, res){
async.waterfall([
function(done){
models.User.update({
password: req.body.password,
resetPasswordToken: null,
resetPasswordExpires: null
}, { where: {
resetPasswordToken: req.body.token,
resetPasswordExpires: {
$gt: moment().format('YYYY-MM-DD HH:mm:ss')
}
}})
// Nodemailer
var transporter = nodemailer.createTransport(sgTransport(options));
var mailOptions = {
from: '"Tester" <test#test.com',
to: 'test#gmail.com', //Replace with Email
subject: 'Your password has been changed',
text: 'Hello,\n\n' +
'This is a confirmation that the password for your account ' + 'test#gmail.com' + ' has just been changed.\n'
};
transporter.sendMail(mailOptions, function(error, info){
if(error){
return console.log(error + 'During Post');
}
console.log('Message sent: ' + info.response);
})
}
])
res.redirect('/login');
});
Here is my login routing:
/*==== Login ====*/
siteRoutes.route('/login')
.get(function(req, res){
res.render('pages/site/login.hbs',{
error: req.flash('error')
});
})
.post(passport.authenticate('local', {
successRedirect: '/app',
failureRedirect: '/login',
failureFlash: 'Invalid email or password.'
}));
Here is the user model:
var bcrypt = require('bcrypt-nodejs');
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('user', {
user_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
field: 'first_name'
},
lastName: {
type: DataTypes.STRING,
field: 'last_name'
},
email: {
type: DataTypes.STRING,
isEmail: true,
unique: true,
set: function(val) {
this.setDataValue('email', val.toLowerCase());
}
},
password: DataTypes.STRING,
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
},
resetPasswordToken: {
type: DataTypes.STRING,
field: 'reset_password_token'
},
resetPasswordExpires: {
type: DataTypes.DATE,
field: 'reset_password_expires'
}
}, {
freezeTableName: true,
classMethods: {
associate: function(db) {
User.belongsToMany(db.Organization, { through: 'member', foreignKey: 'organizationId'})
},
generateHash: function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
},
},
});
return User;
}
Here is the passportjs logic:
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var models = require('../app/models/db-index');
/*==== Passport Configuration ====*/
// Serialize sessions
passport.serializeUser(function(user, done) {
console.log("User ID: " + user.user_id + " is serializing");
done(null, user.user_id);
});
passport.deserializeUser(function(user_id, done) {
models.User.find({where: {user_id: user_id}}).then(function(user){
console.log("User ID: " + user.user_id + " is deserializing");
done(null, user);
}).error(function(err){
done(err, null);
});
});
//Login logic
passport.use('local', new LocalStrategy({
passReqToCallback: true,
usernameField: 'email'
}, function(req, email, password, done) {
console.log("Database query triggered");
//Find user by email
models.User.findOne({
where: {
email: req.body.email
}
}).then(function(user) {
if (!user) {
done(null, false, { message: 'The email you entered is incorrect' }, console.log("Unknown User"));
} else if (!user.validPassword(password)){
done(null, false, console.log("Incorrect Password"));
} else {
console.log("User match");
done(null, user);
}
}).catch(function(err) {
console.log("Server Error");
return done(null, false);
});
}));
//Sign Up Logic
passport.use('local-signup', new LocalStrategy({
passReqToCallback: true,
usernameField: 'email'
}, function(req, email, password, done){
models.User.findOne({
where: {
email: email
}
}).then(function(existingUser){
if (existingUser)
return done(null, false, req.flash('error', 'Email already exists.'));
if (req.user) {
var user = req.user;
user.firstName = firstName;
user.lastName = lastName;
user.email = email;
user.password = models.User.generateHash(password);
user.save().catch(function(err){
throw err;
}).then(function(){
done(null, user, req.flash('error', 'All fields need to be filled in'));
});
} else {
var newUser = models.User.build({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: models.User.generateHash(password)
});
newUser.save().then(function(){
done(null, newUser);
}).catch(function(err){
done(null, false, console.log(err));
});
}
}).catch(function(e){
done(null, false, req.flash('error', 'All fields need to be filled in'),console.log(e.email + e.message));
})
}));
module.exports = passport;
Looks like you forget to generate your password again.
.post(function(req, res){
async.waterfall([
function(done){
models.User.update({
password: models.User.generateHash(req.body.password)