6

I want to save a hashed password. I'm using a setterMethod for this:

module.exports = (sequelize, DataTypes) ->
  sequelize.define 'User',
    # other model fields [...]
    password:
      type: DataTypes.STRING
      validate: notEmpty: msg: 'You need to set a password.'
      set: (pw) ->
        salt = bcrypt.genSaltSync(10)
        hash = bcrypt.hashSync(pw, salt)
        @setDataValue('password', hash)

The setter runs first. An empty string password ('') is hashed into a non-empty one (say $2a$10$pDDIGnV.r47i9YOv0Fls/euQ0yYvfyq8T1SyP9VRQsTUAqptNmxXO).

When the validator validates, the password is not empty anymore.

How can I validate the password before the setter?

I looked into hooks but they don't mention setters either.

I'm using sequelize@2.1.3.

dschwertfeger
  • 286
  • 1
  • 4
  • 21

3 Answers3

1

I solved this problem by using two fields, one being type VIRTUAL that handles the input and validation, and one being type STRING that holds the hashed password.

This example is not coffeescript but you should be able to translate easily.

password_hash: {
  type: DatabaseTypes.STRING,
  allowNull: false,
  validate: {
    notEmpty: true,
  },
},
password: {
  type: DatabaseTypes.VIRTUAL,
  allowNull: false,
  // note that arrow functions cannot access "this", so use the form:
  set: function setPassword(val) {
    // trigger validation on "password" field
    this.setDataValue('password', val);

    // hash the password, this can be done in one step by passing the
    // number of salt rounds instead of the salt string.
    this.setDataValue('password_hash', bcrypt.hashSync(val, 10));
  },
  validate: {
    notEmpty: {
       message: 'You need to set a password.',
    },
  },
},

When you authenticate the user compare the entered password to User.password_hash rather than User.password.

instanceMethods: {
  // authenticate user given a password
  authenticate(password) {
    return bcrypt.compareSync(password, this.password_hash);
  },
},

You can then call this instance method to authenticate a User.

User.findById(userId)
.then((user) => {
  if (user.authenticate(password)) {
    console.log('Authenticated');
  } else {
    console.log('Not authenticated');
  }
});
doublesharp
  • 26,888
  • 6
  • 52
  • 73
0

You can do it with the beforeCreate hook as described here

Essentially, your model code would look something like this

User.init(
  {
    password: {
      type: DataTypes.TEXT,
      allowNull: false,
      validate: {
        len: {
          args: [6],
          msg: "Minimum password length is 6 characters",
        },
        notEmpty: {
          args: [true],
          msg: "Please enter a password",
        },
        notNull: {
          args: [true],
          msg: "Please enter a password",
        },
      },
    },
  },
  {
    hooks: {
      beforeCreate: async (user) =>
        (user.password = await bcrypt.hash(user.password, 10)),
    },
    sequelize,
    modelName: "User",
  },
);

Check the Sequelize hooks API reference here

Saikat Das
  • 358
  • 2
  • 15
0

So I used the beforeSave hook, for both insert and update. I tested it out seems to do the trick - validations work as intended as well.

User.init(
  {
    // attributes
    email: {
      type: Sequelize.STRING,
      allowNull: false,
      unique: true,
      validate: {
        isEmail: {
          msg: "Email address is entered is invalid",
        },
      },
    },
    password: {
      type: Sequelize.STRING,
      allowNull: false,
      validate: {
        notEmpty: {
          msg: "Password cannot be empty",
        },
        notNull: {
          msg: "Password cannot be null",
        },
      },
    },
  },
  {
    hooks: {
      beforeSave: (user, options) => {
        if (options.fields.includes("password")) {
          user.password = bcrypt.hashSync(user.password, 10)
        }
      },
    },
    sequelize,
    modelName: "user",
    underscored: true,
  }
)
blink281
  • 864
  • 7
  • 6