25

I'm trying to use the bcrypt-nodejs package with my sequelize model and was tring to follow a tutorial to incorporate the hashing into my model, but I'm getting an error at generateHash. I can't seem to figure out the issue. Is there a better way to incorporate bcrypt?

Error:

/Users/user/Desktop/Projects/node/app/app/models/user.js:26
User.methods.generateHash = function(password) {
                          ^
TypeError: Cannot set property 'generateHash' of undefined
    at module.exports (/Users/user/Desktop/Projects/node/app/app/models/user.js:26:27)
    at Sequelize.import (/Users/user/Desktop/Projects/node/app/node_modules/sequelize/lib/sequelize.js:641:30)

model:

var bcrypt = require("bcrypt-nodejs");

module.exports = function(sequelize, DataTypes) {

var User = sequelize.define('users', {
    annotation_id: {
        type: DataTypes.INTEGER,
        autoIncrement: true,
        primaryKey: true
    },
    firstName: {
        type: DataTypes.DATE,
        field: 'first_name'
    },
    lastName: {
        type: DataTypes.DATE,
        field: 'last_name'
    },
    email: DataTypes.STRING,
    password: DataTypes.STRING,

}, {
    freezeTableName: true
});

User.methods.generateHash = function(password) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};

User.methods.validPassword = function(password) {
    return bcrypt.compareSync(password, this.local.password);
};
    return User;
}
cphill
  • 5,596
  • 16
  • 89
  • 182
  • Just a note: before you deploy this, try and use the [native bcrypt module](https://www.npmjs.com/package/bcrypt) instead of the bcrypt-nodejs module. This will speed the hashing up a lot because it's implemented in C++ instead of JavaScript. – leroydev Dec 17 '15 at 12:58

5 Answers5

30

Methods should be provided in the "options" argument of sequelize.define

const bcrypt = require("bcrypt");

module.exports = function(sequelize, DataTypes) {
    const User = sequelize.define('users', {
        annotation_id: {
            type: DataTypes.INTEGER,
            autoIncrement: true,
            primaryKey: true
        },
        firstName: {
            type: DataTypes.DATE,
            field: 'first_name'
        },
        lastName: {
            type: DataTypes.DATE,
            field: 'last_name'
        },
        email: DataTypes.STRING,
        password: DataTypes.STRING
    }, {
        freezeTableName: true,
        instanceMethods: {
            generateHash(password) {
                return bcrypt.hash(password, bcrypt.genSaltSync(8));
            },
            validPassword(password) {
                return bcrypt.compare(password, this.password);
            }
        }
    });

    return User;
}
Michael Jasper
  • 7,962
  • 4
  • 40
  • 60
Louay Alakkad
  • 7,132
  • 2
  • 22
  • 45
  • 4
    bcrypt recommend async mode: https://github.com/kelektiv/node.bcrypt.js#why-is-async-mode-recommended-over-sync-mode – alditis Oct 02 '17 at 04:30
  • 4
    Looks like it's important to pay attention to which version of Sequelize you're using here (3 vs 4). In v4 there's a new way to define Instance Methods on the model: http://docs.sequelizejs.com/manual/tutorial/upgrade-to-v4.html#breaking-changes – bryan kennedy Jan 20 '18 at 17:58
  • I am reading the sequelize docs and and came here to find about hash stuff and I just saw this post. I have a question. @Louay Alakkad wrote new methods inside sequelize.define options argument but what about getters and setters? On the docs they suggets use set(value) {this.setValue('password', hash(value))} would this be a bad approach related to Louay's one? – nishi Aug 20 '20 at 23:01
23

Other alternative: Use hook and bcrypt async mode

User.beforeCreate((user, options) => {

    return bcrypt.hash(user.password, 10)
        .then(hash => {
            user.password = hash;
        })
        .catch(err => { 
            throw new Error(); 
        });
});
alditis
  • 4,633
  • 3
  • 49
  • 76
22

There's a tutorial out there on how to get a sequelize/postgreSQL auth system working with hooks and bcrypt.

The guy who wrote the tutorial did not use async hash/salt methods; in the user creation/instance method section he used the following code:

hooks: {
  beforeCreate: (user) => {
    const salt = bcrypt.genSaltSync();
    user.password = bcrypt.hashSync(user.password, salt);
  }
},
instanceMethods: {
  validPassword: function(password) {
    return bcrypt.compareSync(password, this.password);
  }
}    

Newer versions of Sequelize don't like instance methods being declared this way - and multiple people have explained how to remedy this (including someone who posted on the original tutorial):

The original comment still used the synchronous methods:

User.prototype.validPassword = function (password) {
    return bcrypt.compareSync(password, this.password);
};

All you need to do to make these functions asyncronous is this:

Async beforeCreate bcrypt genSalt and genHash functions:

beforeCreate: async function(user) {
    const salt = await bcrypt.genSalt(10); //whatever number you want
    user.password = await bcrypt.hash(user.password, salt);
}

User.prototype.validPassword = async function(password) {
    return await bcrypt.compare(password, this.password);
}

On the node.js app in the login route where you check the password, there's a findOne section:

User.findOne({ where: { username: username } }).then(function (user) {
    if (!user) {
        res.redirect('/login');
    } else if (!user.validPassword(password)) {
        res.redirect('/login');
    } else {
        req.session.user = user.dataValues;
        res.redirect('/dashboard');
    }
});

All you have to do here is add the words async and await as well:

User.findOne({ where: { username: username } }).then(async function (user) {
    if (!user) {
        res.redirect('/login');
    } else if (!await user.validPassword(password)) {
        res.redirect('/login');
    } else {
        req.session.user = user.dataValues;
        res.redirect('/dashboard');
    }
});
user1274820
  • 7,786
  • 3
  • 37
  • 74
2

Bcrypt Is no longer part of node, so I included example with new module of crypto

I am sharing this code from one of working project.

My config file

require('dotenv').config();
const { Sequelize,DataTypes ,Model} = require("sequelize");
module.exports.Model = Model;
module.exports.DataTypes = DataTypes;
module.exports.sequelize  = new Sequelize(process.env.DB_NAME,process.env.DB_USER_NAME, process.env.DB_PASSWORD, {
 host: process.env.DB_HOST,
 dialect: process.env.DB_DISELECT,
 pool: {
   max: 1,
   min: 0,
   idle: 10000
 },
 //logging: true
});

My user model

const { sequelize, DataTypes, Model } = require('../config/db.config');
  var crypto = require('crypto');
  class USERS extends Model {
    validPassword(password) {
      var hash = crypto.pbkdf2Sync(password,
        this.SALT, 1000, 64, `sha512`).toString(`hex`);
      console.log(hash == this.PASSWORD)
      return this.PASSWORD === hash;
    }
  }
  USERS.init(
    {
      ID: {
        autoIncrement: true,
        type: DataTypes.BIGINT,
        allowNull: false,
        primaryKey: true
      },
      MOBILE_NO: {
        type: DataTypes.BIGINT,
        allowNull: false,
        unique: true
      },
      PASSWORD: {
        type: DataTypes.STRING(200),
        allowNull: false
      },
      SALT: {
        type: DataTypes.STRING(200),
        allowNull: false
      }
    },

    {
      sequelize,
      tableName: 'USERS',
      timestamps: true,
      hooks: {
        beforeCreate: (user) => {
          console.log(user);
          user.SALT = crypto.randomBytes(16).toString('hex');
          user.PASSWORD = crypto.pbkdf2Sync(user.PASSWORD, user.SALT,
            1000, 64, `sha512`).toString(`hex`);
        },
      }
    });


  module.exports.USERS = USERS;

And Auth Controller

const { USERS } = require('../../../models/USERS');
module.exports = class authController {
    static register(req, res) {

        USERS.create({
            MOBILE_NO: req.body.mobile,
            PASSWORD: req.body.password,
            SALT:""
        }).then(function (data) {
            res.json(data.toJSON());
        }).catch((err) => {
            res.json({
                error: err.errors[0].message
            })
        })
    }
    static login(req, res) {
        var message = [];
        var success = false;
        var status = 404;
        USERS.findOne({
           where:{
            MOBILE_NO: req.body.mobile
           }
        }).then(function (user) {
            if (user) {
                message.push("user found");
                if(user.validPassword(req.body.password)) {
                    status=200;
                    success = true
                    message.push("You are authorised");
                }else{
                    message.push("Check Credentials");
                }
            }else{
                message.push("Check Credentials");
            }
           
            res.json({status,success,message});
        });
    }
}
0

Old question, but maybe can help someone, you can use sequelize-bcrypt

Example:

const { Sequelize, DataTypes } = require('sequelize');
const useBcrypt = require('sequelize-bcrypt');

const database = new Sequelize({
  ...sequelizeConnectionOptions,
});

const User = database.define('User', {
  email: { type: DataTypes.STRING },
  password: { type: DataTypes.STRING },
});

useBcrypt(User);

Usage

User.create({ email: 'john.doe@example.com', password: 'SuperSecret!' });
// { id: 1, email: 'john.doe@example.com', password: '$2a$12$VtyL7j5xx6t/GmmAqy53ZuKJ1nwPox5kHLXDaottN9tIQBsEB3EsW' }

const user = await User.findOne({ where: { email: 'john.doe@example.com' } });
user.authenticate('WrongPassword!'); // false
user.authenticate('SuperSecret!'); // true
Mattia M.
  • 464
  • 1
  • 4
  • 16