Aaron Ullal
Aaron Ullal

Reputation: 5235

Sequelize instance methods not working

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 :

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?

Upvotes: 22

Views: 18799

Answers (5)

daronwolff
daronwolff

Reputation: 2064

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;
};

Upvotes: 2

Tetranyble
Tetranyble

Reputation: 75

as 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: '[email protected]',
    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: '[email protected]',
    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: '[email protected]' },
    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

Upvotes: 1

AlexM
AlexM

Reputation: 131

For 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);
};

Upvotes: 13

Sibin John Mattappallil
Sibin John Mattappallil

Reputation: 1789

We 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);
};

Upvotes: 10

user1695032
user1695032

Reputation: 1142

I think you are using the sequelize model definition api incorrectly. http://docs.sequelizejs.com/en/latest/docs/models-definition/#expansion-of-models

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: ...
  }
});

Upvotes: 14

Related Questions