How to set automatic password hashing in Sequelize's user model? - node.js

Right now I'm hashing the password on the route function and providing the hash when creating the user but I know there's a way to have this be handled through Sequelize itself. I have searched around but every answer seems to be outdated or the methods never got called. Here is my config:
server/models/User.js
module.exports = (sequelize, type) => {
const User = sequelize.define(
"User",
{
user_id: {
type: type.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: type.STRING,
allowNull: false
},
email: {
type: type.STRING,
allowNull: false
},
password: {
type: type.STRING,
allowNull: false
},
reg_date: {
type: type.DATEONLY,
allowNull: false,
defaultValue: sequelize.fn("now")
}
},
{
timestamps: false
}
);
return User;
};
server/config/sequelize.js
const Sequelize = require("sequelize");
const sequelize = new Sequelize(process.env.CLEARDB_DATABASE_URL);
sequelize
.authenticate()
.then(() => {
console.log("Connection has been established successfully.");
})
.catch(err => {
console.error("Unable to connect to the database:", err);
});
const UserModel = require("../models/User");
const User = UserModel(sequelize, Sequelize);
module.exports = User;
This is how I'm handling hashing right now:
server/routes/register.js
User.findOne({ where: { email: email } }).then(user => {
if (!user) {
bcrypt.hash(password, 10, (err, hash) => {
if (err) throw err;
User.create({
name: req.body.name,
email: email,
password: hash
})
.then(user => {
return user;
})
.catch(err => console.log(err));
});
}
});

This did the trick:
...
{
timestamps: false
}
);
User.addHook(
"beforeCreate",
user => (user.password = bcrypt.hashSync(user.password, 10))
);
return User;
};

Please don't mix your model definition with a business or a security logic (or another one). In future you might want to change an encryption library or a hashing algorithm and you will have to change your model accordingly. A security layer should be separated from your models.

Related

Nodejs and sequelize : Error .find is not a function

I am a nodeJs developer, I failed to get the list of users, when I tested my code on postman it gives me the error below:
User.find is not a function
The following is my user.js file
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
static associate(models) {
}
}
User.init({
username: {
type:DataTypes.STRING,
allowNull: false,
},
email: {
type:DataTypes.STRING,
allowNull: false,
},
password: {
type:DataTypes.STRING,
allowNull: false,
},
role: {
type:DataTypes.STRING,
allowNull: false,
},
}, {
sequelize,
tableName:'users',
modelName: 'User',
});
return User;
};
and this is where it's imported/required
const router = require('express').Router();
let User = require('../models/user');
router.route('/getUser').post((req, res) => {
User.find({})
.then(users => res.json(users))
.catch(err => res.status(400).json('Error: ', err));
});
The user.js file that you provided is exporting a function that create the User class (and not actually the User model class)
Did you already init this class elsewhere and you are importing the wrong file ?
It didn't work, I did as you told me but it gives me back:
sequelize id not defined
Also I don't think that the problem is in the model because I have used it before and in the login I used .find and it worked without any problem!
this login code :
router.post('/login', async (req,res,next) => {
const user = await User.findOne({ where: { email: req.body.email }});
if (user) {
const password_valid = await bcrypt.compare(req.body.password,user.password);
if (password_valid) {
token = JWT.sign({
isLogin: true,
"id": user.id,
"email": user.email,
"username": user.username
},"12345678");
res.status(200).json({token :token});
} else {
res.status(400).json({isLogin : false, error: "password incorrect" });
}
} else {
res.status(404).json({isLogin : false, error :"User does not exist"});
}
});

Sequelize class method works fine but instance method is not working

I've defined a user model in Sequelize, and also defined a custom class method and an instance method for it. I'm calling these two methods in my login api (which works fine). The problem is that the class method works perfectly, but the instance method results an error, and I cannot recognize what is wrong with my code. PLEASE HELP.
This is my user model and its methods:
const Sequelize = require("sequelize");
const sequelize = require("../db/db.config");
const bcrypt = require("bcryptjs");
const _ = require("lodash");
const jwt = require("jsonwebtoken");
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
first_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
last_name: {
type: Sequelize.STRING(50),
allowNull: false,
},
email: {
type: Sequelize.STRING(50),
allowNull: false,
validate: {
isEmail: true,
},
},
password: {
type: Sequelize.STRING(100),
allowNull: false,
},
});
User.prototype.testMethod = function () {
console.log("THIS IS A TEST");
};
User.beforeCreate(async (user, options) => {
const hashedPassword = await bcrypt.hash(user.password, 10);
user.password = hashedPassword;
});
User.findByEmailAndPassword = async function (inputEmail, inputPassword) {
try {
const user = await User.findOne({ where: { email: inputEmail } });
if (user === null) {
return null;
}
const passwordMatch = await bcrypt.compare(inputPassword, user.password);
if (!passwordMatch) {
return null;
}
return _.pick(user, "id", "first_name", "last_name", "email");
} catch (error) {
console.log("FIND BY EMAIL AND PASSWORD ERROR: ", error);
}
};
module.exports = User;
And this is my login router:
const express = require("express");
const router = express.Router();
const User = require("../models/user.model");
router.post("/api/login", async (req, res) => {
try {
const user = await User.findByEmailAndPassword(
req.body.email,
req.body.password
);
console.log("USER: ", user);
await user.testMethod();
if (!user) {
return res.status(400).send({
errorMessage: "Username and password combination is not correct!",
});
}
return res.status(200).send(user);
} catch (error) {
res.status(400).send({ errorMessage: error });
}
});
module.exports = router;
Thanks.
First, the reason that you get the issue is that findByEmailAndPassword is returning the regular object from _.pick and you are defining the instance method for Sequelize instance. This instance method can be callable on Sequelize instance and not on regular object.
However, your goal is
What I'm trying to do here is to avoid sending user password in my response body.
defaultScope is great for this use case. It allows you to define some repetitive options on a model.
You can define your User model as
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
...
}, {
defaultScope: {
attributes: {
exclude: ['password']
}
}
});
Defining the defaultScope on the model, this will be applied to many Sequelize functions by default.
Scopes apply to .find, .findAll, .count, .update, .increment and .destroy.
I also tested that it also applied to .findByPk, .findOne.
So, how to use...
Call regular Sequelize findOne function.
const user = User.findOne({
where: {
email: req.body.email,
password: req.body.password
}
});
By default, since defaultScope is applied, this won't return password in response.
In some scenarios where you need to return the password, use unscoped to disable the defaultScope.
// This will return `password` in response.
User.unscoped().fineOne(...)
For reference: https://sequelize.org/master/manual/scopes.html

How to implement Instance Methods in Sequelize 6 with Node.js

So i was implementing a bit of change in my server application - switching databases from MongoDb to PostgreSQL (with Sequelize 6) and i had to change the controller functions i had created for mongoose to suit the current database but there was a problem implementing the instance methods, but as usual there was little to no helpful solutions online for this with Sequelize 6. But now there is. Below are the code samples for some of the problems and error messages you may be facing if you come across this post.
The function which calls the instance method:
userController.js (login function)
User.findByPk(req.body.id)
.then(user => {
if (!user) {
return res.status(401).send('Sorry!! You do not have an account with us.')
}
if (!user.validPassword(req.body.password)) {
return res.status(401).send('Invalid Password')
} else {
res.status(200).json({ user })
}
})
.catch(error => {
console.log(error)
res.status(500).send({
message: 'Some error occurred while logging in this User',
error: error.message
});
});
EXAMPLE CODE 1
user.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
return User;
};
response (Server 500 error message)
{
"message": "Some error occurred while logging in this User",
"error": "user.validPassword is not a function"
}
The instance method in this case is not recognized and the function validPassword() is not run thus a 500 Server error. Let's move to example 2.
EXAMPLE CODE 2
user.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
User.prototype.validPassword = (password) => {
return bcrypt.compareSync(password, this.password);
};
return User;
};
response (Server 500 error message)
{
"message": "Some error occurred while logging in this User",
"error": "Illegal arguments: string, undefined"
}
The instance method in this case is still not recognized and the function validPassword() is thus not run because over here the parameter this.password for the bcrypt.compareSync() function is not defined (or has been used outside the Model extension) thus a 500 Server error.
And now for the solution.
After around half a day of searching, I found out that for some reason the instanceMethods functionality has been removed in Sequelize v4. As a result, the only way to obtain this functionality is by one of the following:
declaring the function on the model class as Jeffrey Dabo suggested
adding the function on the prototype of the Sequelize model
Very important: If you go with the prototype approach, in order to have access to the this object, you need to declare the function using the function syntax and not as an arrow function, or else it will not work.
Example:
const User = sequelize.define('User', {...});
User.prototype.validatePassword = function (password) {
return bcrypt.compareSync(password, this.password);
}
Placing the instance method function right under the class method function (associations on models) would eventually allow your function validPassword() to be recognized, run and produce the desired response.
user.js
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
validPassword(password) => {
return bcrypt.compareSync(password, this.password);
};
};
User.init({
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password_confirmation: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
}
sequelize,
modelName: 'User',
});
return User;
};
I don't think this will help you but I can see that you have a syntax error in your codes, try adding , before
sequelize, modelName: 'User'
{
hooks: {
beforeCreate: (User) => {
const salt = bcrypt.genSaltSync();
User.password = bcrypt.hashSync(User.password, salt);
User.password_confirmation = User.password;
}
},
instanceMethods: {
validatePassword: (password) => {
return bcrypt.compareSync(password, this.password);
}
},
sequelize,
modelName: 'User',
}

How does Sequelize destroy function work?

I've been trying to delete a user in a MySql database using Sequelize ORM but it's not deleting.
without paranoid, here's the query generate:
UPDATE `Users` SET `active`=?,`updatedAt`=? WHERE `id` = ?
with paranoid set to true, here's the query generate:
UPDATE `Users` SET `active`=?,`updatedAt`=? WHERE (`deletedAt` IS NULL AND `id` = ?)
digging around the internet i found out that the the query is supposed to take this form UPDATE "posts" SET "deletedAt"=[timestamp] WHERE "deletedAt" IS NULL AND "id" = 1 accounding to this Documentation.
Here's my model:
'use strict';
const sequelizePaginate = require('sequelize-paginate')
const bcrypt = require('bcrypt')
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
photo: DataTypes.STRING,
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: DataTypes.STRING,
phone: DataTypes.STRING,
password: DataTypes.STRING,
role: DataTypes.STRING,
verifyEmailAt: DataTypes.DATE,
referralCode: DataTypes.STRING,
active: DataTypes.BOOLEAN,
referrer: DataTypes.STRING,
rememberToken: DataTypes.STRING
}, { instanceMethods: {
comparePasswords: (password, prevPassword,callback) => {
bcrypt.compare(password, prevPassword, (error, isMatch) => {
if(error) {
return callback(error);
}
return callback(null, isMatch);
});
}
},
hooks: {
beforeValidate: (user) => {
if(user.changed('password')) {
return bcrypt.hash(user.password, 10).then(function(password) {
user.password = password;
});
}
}
}});
User.associate = (models) => {
// associations can be defined here
models.User.hasMany(models.Booking)
models.User.hasMany(models.DriversLocation)
models.User.hasMany(models.DriverReview)
models.User.hasMany(models.Payment)
};
sequelizePaginate.paginate(User)
return User;
};
And also my query snippet:
const { validationResult } = require('express-validator')
const models = require('../models')
deleteAdmin: (req, res) => {
const result = validationResult(req);
if (!result.isEmpty()) {
return res.status(422).json({ success: false, message: 'input validation failure',
code: 422, properties:{ userId : req.params.userId}, error: result.array() });
}
models.User.destroy({
where: {
id: req.params.userId
}
})
.then((data) => {
return res.status(200).json({ success: true, message: "success",
code: 200, properties:{params: {userId: req.params.userId }},
data: {content: data}});
}).catch((error) => {
return res.status(520).json({ success: false, message: "unknown error",
code: 520, properties:{params: {userId: req.params.userId }},
data: { error: error} });
})
}
Is there something am doing wrong here?
Thank you for your help in advance
destroy usage seems fine to me. Please verify your User model import & req params values.
Also, there is no id field defined in schema definition. How is id being saved in DB record ?

how to encrypt passwords with beforeBulkCreate hook in sequelize

I´m trying to encrypt password before bulk create with sequelize.
const bcrypt = require('bcryptjs');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('users', {
username: {
type: DataTypes.STRING,
allowNull: false,
required: true
},
password: {
type: DataTypes.STRING,
allowNull: false,
required: true
},
},
{
freezeTableName: true,
hooks: {
beforeBulkCreate: function(records) {
records.forEach((user, index) => {
return bcrypt.hash(user.password, 10)
.then(hash => {
user.password = hash;
console.log('password hash:', user.password);
})
.catch(err => {
throw new Error();
});
})
},
beforeCreate: (user) => {
return bcrypt.hash(user.password, 10)
.then(hash => {
user.password = hash;
})
.catch(err => {
throw new Error();
});
}
}
});
User.prototype.validPassword = (password) => {
return bcrypt.compareSync(password, this.password);
};
return User;
}
hooks is called but the password that´s store in the database is the plain one not the new one
const userData = [
{ username: 'John', password: '123' },
{ username: 'Mary', password: '321' },
];
User.bulkCreate(userData, { returning: true })
.then((result) => {
console.log('User data success');
})
.catch((error) => {
console.log(error);
});
I also tried passing the { individualHooks: true } option but doing this records are not being inserted at all.
I came here in search of an answer to this ~ 3 years old question. Chances are that >= 3 years later someone else will come looking for the same.
Here, I solved mine using bcrypt.hashSync()
I know they recommend using the asynchronous bcrypt.hash() solution but sometimes most optimal isn't always the solution.
You can learn more here bcrypt npm docs
hooks: {
beforeBulkCreate: (users) => {
users.forEach((user) => {
// to see the properties added by sequelize
console.table(user);
// now modify the "dataValues" property
user.dataValues.password = bcrypt.hashSync(user.password, 10);
});
},
// other hooks here
},

Resources