Forcing Sequelize to update req.user in Express route - node.js

I have a very simple site that is using Passport JS to create local login strategy to hit a local postgres database using the Sequelize ORM.
The user model looks something like this:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
id: {
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
email: DataTypes.STRING,
password: DataTypes.STRING,
}, {
classMethods: {
generateHash: function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
}
},
getterMethods: {
someValue: function() {
return this.someValue;
}
},
setterMethods: {
someValue: function(value) {
this.someValue = value;
}
}
});
}
Everything seems to work just fine. I can sign up using this strategy, log in, and see data.
I also am using Express and have various routes set. The req.user appears to be set correctly, as I can interact with all the fields of this object.
Consider the sample route which works correctly:
app.get('/profile', isLoggedIn, function(req, res) {
res.render('profile.ejs', {
user : req.user
});
});
My serialization / deserialization methods:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id).then(function(user) {
done(null, user);
}).catch(function(e) {
done(e, false);
});
});
So, as per the Passport JS documentation, the user session seems to be correctly set and hooked into Express.
The trouble is that I cannot update any of the fields in the user object.
If I have the following route:
app.get('/change-email', function(req, res) {
req.user.email = req.body.email;
res.status(200).end();
});
Nothing happens in the database.
This is very similar to this question except it appears with Sequalize, the user object never persists, even after logging out and back in again.
I have also tried: req.session.passport.user.email = req.body.email
Although I didn't think this would fix the problem, I also tried to call login with the new user object, but this generated 500 errors. Which such a limited number of functions that can be called, according to the Passport JS documentation, I'm starting to question if this functionality is even possible.
I'm not sure what to try from here. How can I update my user object using Sequelize?
Any help would be greatly appreciated! Thanks!

// Edit: Rewriting first paragraph to be clearer
When you change any column values in an Instance object you also need to explicitly save the changes to the database to persist them. Since you are already storing the user Instance in req.user via passport.deserializeUser you only need to make a small change to your routing code to do this.
Instead of your current route for /change-email, I suggest:
app.get('/change-email', function(req, res) {
req.user.email = req.body.email;
req.user.save()
.then(function() {
res.status(200).end();
});
});
For more information on how to persist changes to an instance, see this part of the Sequelize documentation.

I know it's a late answer but no wonder someone will pass around this in the near future,
anyway, to get the authenticated user information in req.user with sequelize version 6.* and Express
here is the trick:
//a middleware to verify if a user is authenticated or not
exports.verifyToken = async (req, res, next) => {
let token = req.headers.bearer;
if (!token) {
return res.status(403).send({
message: "No token provided!"
});
}
jwt.verify(token, secretKey, async (err, decoded) => {
if (err) {
return res.status(401).send({
message: "Unauthorized!"
});
}
//$$$$$$$$$ here is everything you want to do $$$$$$$$$$$$$$$
req.user = await Users.findByPk(decoded.id, { raw: true });
next();
});
};
and here is an example where we can use that middleware
.get('/wishlist', auth.verifyToken, (req, res, next) => {
console.log(req.user);
})
the output will be something like this:
{
id: '9313e6e5-7b04-4520-8dbc-d04fad3a0cb1',
fullName: 'Anis Dhaoui',
avatar: 'images/imagelink.jpg',
username: 'anis',
}
and of course, you can modify the output of your findByPk or findOne query using include or exclude see Sequelize docs
https://sequelize.org/docs/v6/core-concepts/model-querying-basics/

Related

controller isnt working as it is supposed to

I used MVC to make a NodeJS server and this is one of the controllers:
module.exports.create_user = async function (req, res) {
// console.log(req.body);
// console.log(req.user);
await Company.findOne({ user: req.body.user }, function (err, user) {
if (user) {
return res.redirect('/login');
}
else {
if (req.body.password == req.body.confirm_password) {
Company.create({
"country": req.body.country,
"username": req.body.user,
"password": req.body.password
});
}
else {
console.log('Passwords didnt match');
}
}
});
req.session.save(() => {
return res.redirect('/profile');
})
}
What this code supposed to do?
It searches if a user already exists; if yes, it will redirect to /login.
If no such user exists, it should create a new user and redirect to /profile.
What does this code do?
Regardless of whether the user exists or not, the code always redirects to /login. Also, a user is created in the database, so every time a new user wants to signup, the user needs to signup and then go to sign in to get access to /profile
What is the problem here which doesn't allow redirect to /profile? And how to fix it?
Let me know if you need anything else
Use username instead of user to find a user
Company.findOne({ username: req.body.user });
You are mixing callback style with async/await, await keyword does not affect on your, it will not wait until the query finished. await keyword just working when you wait for a Promise like object (then able object).
I guess you are using mongoose, the current version of mongoose supports Promise return style.
module.exports.create_user = async function (req, res) {
// console.log(req.body);
// console.log(req.user);
try {
// Use `username` instead of `user` to find a user
const user = await Company.findOne({ username: req.body.user }); // callback is not passed, it will return a Promise
if (user) {
return res.redirect('/login');
}
if (req.body.password == req.body.confirm_password) {
await Company.create({ // wait until user is created
"country": req.body.country,
"username": req.body.user,
"password": req.body.password
});
// then redirect page
req.session.save(() => {
return res.redirect('/profile');
});
} else {
console.log('Passwords didnt match');
// what happen when password didn't match
// return res.redirect('/login'); ???
}
} catch (error) {
// something went wrong
// return res.redirect('/login'); ???
}
}
passport.checkAuthentication = async function (req, res, next) {
console.log(req.user);
let auth_status = await req.isAuthenticated() ? "sucess" : "failure";
console.log(`Authentication ${auth_status}`);
// if the user is signed in, then pass on the request to the next function(controller's action)
if (await req.isAuthenticated()) {
return next();
}
// if the user is not signed in
return res.redirect('/login');
}
I did a but more work on this and possibly the controller is working fine and the problem could be in middleware. In the signup case discussed above, the middelware always logs 'Authentication failure' to console.

NodeJS, getting username of logged in user within route

I am getting in to NodeJS and have followed some video tutorials for making stuff, to understand NodeJS and Express. It turned more in to copying as little were explained, so tried to make my own thing using what I learned and so on.
Making a simple login function with PassportJS, ExpressJs and Mongoose.
The login and stuff works, and I can get the username of the currently logged in user and display it if I define it within the main app.js using this:
app.get("/stuff", (req,res) => {
res.render("stuff.html", {username:req.user.username});
});
Now if I want to make nice and structured by using router, I cannot get it to work. It throws error saying username is undefined, making page unable to render. The router itself works if I don't pass any variable or use variables I know will work (e.g. var x = "Hello"; res.render … {msg:x});).
Part of the app.js that handle routes:
var stuff = require("./routes/stuff");
app.use("/stuff", stuff);
module.exports.app;
I've tried to cont x = require("…") basically everything that is in the app.js in this stuff.js file, but to no avail, so removed everything but express + routes to get fresh start.
How do I pass the username that is working in app.js in to the routed file? Preferably make it automatically do to every page if possible, using app.get("*")… or something.
Entire stuff.js:
/* Routes */
const express = require("express");
const router = express.Router();
/* Stuff */
router.get("/", function(req, res, next) {
res.render("stuff.html", {username:req.user.username});
console.log(req.user.username);
next();
});
/* Bottom */
module.exports = router;
Login section of app.js:
app.post('/login',
passport.authenticate('local',
{
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: 'Wrong login'
}
), function(req,res) {
console.log("Hello " + req.user.username);
});
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,callback) {
User.getUserByUsername(username, function(err,user) {
if(err) throw err;
if(!user) {
return callback(null, false, {msg: "shit"});
}
User.comparePassword(password, user.password, function(err,isMatch) {
if(err) return callback(err);
if(isMatch) {
return callback(null, user);
} else {
return callback(null, false, {msg:"Something"});
}
});
});
}));
The users.js file for handling registering new users, if that's relevant:
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/users");
const db = mongoose.connection;
mongoose.Promise = global.Promise;
const bcrypt = require("bcryptjs");
/* Data schema */
const userSchema = mongoose.Schema({
name: {
type: String
},
username: {
type: String,
index: true
},
password: {
type: String
},
email: {
type: String
}
});
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.getUserById = function(id, callback) {
User.findById(id, callback);
}
module.exports.getUserByUsername = function(username, callback) {
var query = {username: username};
User.findOne(query, callback);
}
module.exports.comparePassword = function(testPw, hash, callback) {
bcrypt.compare(testPw, hash, function(err,isMatch) {
callback(null,isMatch);
});
}
As far as I understand you are trying to pass your username to the preferably every file including your router file. What I do for this to use middleware in app.js to pass every page. Or you can simply implement passport implementation in the other page as well which could be useless i guess.
app.use(function(req,res,next){
res.locals.currentUser=req.user
next()
}
Then, you can use use your currentUser in every page when you try to render.
I encountered the same issue, probably after following the same tutorials...
I found that the function you need in the app.js is:
app.get('*', function(req, res,next){
res.locals.user = req.user || null;
next();
})
it should be in the app.js already. Now, in all of the other page you should be able to use req.user.username

Unhandled rejection TypeError: dbUser.validPassword is not a function Sequelize Node React Passport

I am attempting to use Passport Authentication on my web app. I am using Sequelize ORM, Reactjs front-end and express and node back end. Right now, when I register a user everything works fine. the problem comes when I try to login. I see the user querying the DB to find the user with correct email, but when it is time to compare passwords, i am catching an error.
"Unhandled rejection TypeError: dbUser.validPassword is not a function"
here is my config/passport.js file:
var passport = require("passport");
var LocalStrategy = require("passport-local").Strategy;
var db = require("../models");
// Telling passport we want to use a Local Strategy. In other words, we
want login with a username/email and password
passport.use(new LocalStrategy(
// Our user will sign in using an email, rather than a "username"
{
usernameField: "email"
},
function(email, password, done) {
// When a user tries to sign in this code runs
db.User.findOne({
where: {
email: email
}
}).then(function(dbUser) {
// If there's no user with the given email
if (!dbUser) {
return done(null, false, {
message: "Incorrect email."
});
}
// If there is a user with the given email, but the password the user
gives us is incorrect
else if (!dbUser.validPassword(password)) {
return done(null, false, {
message: "Incorrect password."
});
}
// If none of the above, return the user
return done(null, dbUser);
});
}
));
// In order to help keep authentication state across HTTP requests,
// Sequelize needs to serialize and deserialize the user
// Just consider this part boilerplate needed to make it all work
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
// Exporting our configured passport
module.exports = passport;
Here is my User Model:
var bcrypt = require("bcrypt-nodejs");
[![enter image description here][1]][1]module.exports = function(sequelize, DataTypes){
var User = sequelize.define("User", {
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
},{
classMethods: {
associate: function(models) {
User.hasOne(models.Educator, {
onDelete: "cascade"
});
User.hasOne(models.Expert, {
onDelete: "cascade"
});
}
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
}
},
// Hooks are automatic methods that run during various phases of the User Model lifecycle
// In this case, before a User is created, we will automatically hash their password
hooks: {
beforeCreate: function(user, options) {
console.log(user, options )
user.password = bcrypt.hashSync(user.password, bcrypt.genSaltSync(10), null);
}
}
})
return User;
}
I am also including an image of the error.
As of sequelize version >4, it has changed the way instance methods are defined.
They follow a more class based approach now,
A sample from the Docs for how it has to be done
const Model = sequelize.define('Model', { ... });
// Class Method
Model.associate = function (models) { ...associate the models };
// Instance Method
Model.prototype.someMethod = function () {..}
The syntax you are using corresponds to sequelize < 4.

Passport Local Remember Me Strategy

I am trying to create a passport remember me strategy but I am not sure how to call it. My overall strategy is to store two tokens in my database and as cookies on the client's computer and compare then to verify that they are real users. I am currently attempting to pass app.use a passport.authenticate strategy so that I can verify success of failure using my strategy.
In my app.js file I have:
passport.use('rememberMe',new passportLocal.Strategy({ passReqToCallback: true },
(req, cb) => {
//check req.cookies['token']...
return cb(null, (rememberMe.checkPersistance(req.cookies['token'], req.cookies['statictoken'])));
}));
app.use((req, res) => passport.authenticate('rememberMe'), (req, res) => {
//successfully logged in!
})
Note: rememberMe.checkPersistance does the comparison against the database and returns a true or false.
My problem is that I don't think I am using the app.use syntax correctly and I am not sure what the correct way to do it. How do I use passport.authenticate when it isn't in a .POST function?
I figured out the answer to this question and overall I only had this problem because I didn't understand how .get and .post worked. For both each function you pass it, the function can pick up request, response, and next.
So you can replace .post with .get for most examples of passport you will see online. The difference between them will be what is post is designed to be sent data and then return something (like login information) while get is designed to be a way to query some information. Here is more detailed explanation.
Create a Schema for Tokens
'use strict'
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
const TokenSchema = Schema({
value: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true
}
});
module.exports = mongoose.model('token', TokenSchema);
Then define your strategie
passport.use(new RememberMeStrategy(
function(token, done) {
Token.findOneAndRemove({ value: token })
.populate('user')
.exec( function (err, doc) {
if(err) return done(err);
if(!doc) return done(null,false);
return done(null, doc.user);
});
},
function(user, done) {
crypto.randomBytes(64, (err, buf) => {
const value = buf.toString('hex');
const token = new Token({
value: value,
user: user._id
});
token.save((err) => {
if (err) return done(err);
console.log(value);
return done(null, value)
});
});
}
));
I have found an issue : i can't logged out after define this strategie and check the remember me box.
I just want the form loggin to be autofilled when i come back but it seems this module is useless, it not have the behaviour I want.

Nodejs - User Data on login to front-end

I have build a couple other expressjs applications, but I just can't find how to pass the User Model to the front-end or put it in the req as a parameter.
The app is a one page web app, so the user uses the login form to post to /login:
app.post('/login', require('./app/controllers/user').login);
the controller picks it up and calls the specific module in order to handle the request:
exports.login = function (req,res) {
AccountHandler.login(req,function (response) {
if (response.code < 0) {
req.flash('error', response.msg);
}
else if (response.code > 0) {
req.flash('success', response.msg);
}
req.user = response.user;
res.redirect(response.url);
});
}
and here is the module handling the login and calling the callback by passing the required arguments:
exports.login = function (req,callback) {
process.nextTick(function () {
User.findOne({ 'valid.email': req.body.Email }, function (err, user) {
if (err) {
callback({url: '/#login', msg: "There was an unexpected error!", code: -10});
}
if (!user) {
callback({url: '/#login', msg: "No such email/password combination was found", code: -1});
}
if (user) {
easyPbkdf2.verify(user.valid.salt, user.valid.password, req.body.Password, function (err, valid) {
if (!valid){
callback({url: '/#login', msg: "No such email/password combination was found", code: -1});
}
else{
callback({user: user, url: '/', msg: "acknowledged", code: 10});
}
});
}
});
});
}
In the Controller I am saying req.user = response.user; which doesn't persist, and when the user is redirected to '/' the req.user is still empty. how can I keep this user information to the redirect page?
If my understanding is correct, the res.redirect() call will actually cause the
browser to redirect to the given url, which will result in a new request to
your express server. Since its a new request, the old req object is no longer
relevant.
I think what you want is to store the logged in user's session using express session middleware. Lots of good examples are out there.. Here is one.
So the solution is to use session + middleware logic/functionality.
Here is the setup (exrepssjs):
Using the express-session module:
var sessionMiddleware = session({
resave: true,
saveUninitialized: true,
httpOnly: true,
genid: function(req) {
return uuid.v1() // I used another module to create a uuid
},
secret: 'random secret here',
cookieName: 'session', // if you dont name it here it defaults to connectsid
});
following create a middleware, so that you process the session on every request as follows (simplified, i do a lookup on the user overtime and pass it back to the front end):
app.use(function(req, res, next) {
res.locals.success_messages = req.flash('success_messages');
res.locals.error_messages = req.flash('error_messages');
if (req.session && req.session.user) {
User.findOne({ email: req.session.user.email }, function(err, user) {
if (user) {
req.user = user;
delete req.user.password; // delete the password from the session
req.session.user = user; //refresh the session value
res.locals.user = user;
}
// finishing processing the middleware and run the route
next();
});
} else {
next();
}
});
Now from the front-end most of the time using either ejs or jade you can access the user using the "user" variable.

Resources