I am trying to use Sequelize's instance method to validate a password on login attempt. I have defined the User model as :
var User = sequelize.define('User',{ id:{ type:DataTypes.BIGINT, autoIncrement: true, allowNull: false, primaryKey:true }, username:{ type:DataTypes.STRING, unique:true }, password:{ type: DataTypes.STRING }, ... }, { classMethods:{ associate:function(models){ ... } } }, { instanceMethods:{ validatePassword:function(password){ return bcrypt.compareSync(password, this.password); } } } ); return User; } In my login route I do the following :
- 1) Retrieve username & password from request body
- 2) Check if username exists in database
- 3) If user exists, get user object and compare sent password with hashed password in database using validatePassword method.
Here is the relevant code
var username = req.body.username || ""; var password = req.body.password || ""; models.User.findOne({ where: {username: username} }). then( function(user) { if(user){ console.log(user.validatePassword(password)); } .... Each time I try to login I get the following error
[TypeError: user.validatePassword is not a function] What am I doing wrong?
25 Answers
I think you are using the sequelize model definition api incorrectly.
This is the correct way:
var User = sequelize.define('User',{}, { classMethods: { method1: ... }, instanceMethods: { method2: ... } }); not like this:
var User = sequelize.define('User',{}, { classMethods: { method1: ... } },{ instanceMethods: { method2: ... } }); 1For anyone who's having a similar problem, I ran into the same issue but using Sequelize 5.21.5. According to this article, Sequelize Instance Methods, starting with Sequelize 4.0 and above, you have to use the prototype methodology in order to define instance methods like so:
// Adding an instance level methods. User.prototype.validPassword = function(password) { return bcrypt.compareSync(password, this.password); }; 2We can add instanceLevelMethods to prototype,
User.prototype.your-instance-level-method-name = function() { return 'foo'; }; I did it like this:
// Adding an instance level methods. User.prototype.validPassword = function(password) { return bcrypt.compareSync(password, this.password); }; 1as at sequelize "sequelize": "^5.21.7" accessing the instanceMethods as shown by @user1695032 returns undefined.
here's what i found after several hours of getting undefined in the console.log() passing in the user object return from the query below:
User { dataValues: { id: 1, firtName: null, lasteName: null, email: '', phone: null, password: '$2b$10$yEWnBFMAe15RLLgyU3XlrOUyw19c4PCmh8GJe9QVz3YkbdzK5fHWu', createdAt: 2020-05-27T21:45:02.000Z, updatedAt: 2020-05-27T21:45:02.000Z }, _previousDataValues: { id: 1, firtName: null, lasteName: null, email: '', phone: null, password: '$2b$10$yEWnBFMAe15RLLgyU3XlrOUyw19c4PCmh8GJe9QVz3YkbdzK5fHWu', createdAt: 2020-05-27T21:45:02.000Z, updatedAt: 2020-05-27T21:45:02.000Z }, _changed: {}, **_modelOptions: {** timestamps: true, validate: {}, freezeTableName: false, underscored: false, paranoid: false, rejectOnEmpty: false, whereCollection: { email: '' }, schema: null, schemaDelimiter: '', defaultScope: {}, scopes: {}, indexes: [], name: { plural: 'Users', singular: 'User' }, omitNull: false, **instanceMethods: { comparePasswords: [Function: comparePasswords] },** hooks: { beforeValidate: [Array] }, sequelize: Sequelize { options: [Object], config: [Object], dialect: [MysqlDialect], queryInterface: [QueryInterface], models: [Object], modelManager: [ModelManager], connectionManager: [ConnectionManager], importCache: [Object] } }, _options: { isNewRecord: false, _schema: null, _schemaDelimiter: '', raw: true, attributes: [ 'id', 'firtName', 'lasteName', 'email', 'phone', 'password', 'createdAt', 'updatedAt' ] }, isNewRecord: false } the code before the error:
models.User.findOne({where: {email: req.body.email}}).then((user)=>{ console.log(user) if(!user) { res.status(401).json({ message: 'Authentication failed!' }); } else { user.comparePasswords(req.body.password, (error, isMatch) =>{ console.log(error + ' -- ' + isMatch) if(isMatch && !error) { const token = jwt.sign( { username: user.username }, keys.secret, { expiresIn: '30h' } ); res.status(200).json({ success: true,message: 'signed in successfully', token: 'JWT ' + token }); } else { res.status(401).json({ success: false, message: 'Login failed!' }); } }); } }).catch((error)=>{ console.log(error) res.status(500).json({ success: false, message: 'There was an error!'}); }) this cause TypeError: user.comparePasswords is not a function
after changing this line:
** user.comparePasswords(req.body.password, (error, isMatch) =>{} ** to this:
** user._modelOptions.instanceMethods.comparePasswords(req.body.password, (error, isMatch) =>{}** booooom! everything worked
I use this approach:
const bcrypt = require('bcrypt-nodejs'); const constants = require('../constants/users'); module.exports = (Sequelize, type) => { const User = Sequelize.define(constants.TABLE_NAME, { username: { type: type.STRING, unique: true, allowNull: false, }, password: { type: type.STRING, allowNull: false, }, // bla bla }); const setSaltAndPassword = async function(user) { if (user.changed('password')) { const salt = bcrypt.genSaltSync(constants.PASSWORD_SALT_SIZE); user.password = bcrypt.hashSync(user.password, salt); } }; User.prototype.validPassword = async function(password) { return await bcrypt.compare(password, this.password); }; User.beforeCreate(setSaltAndPassword); User.beforeUpdate(setSaltAndPassword); return User; }; 0