43
votes

I'm using Sequelize to do a DB find for a user record, and I want the default behavior of the model to not return the password field for that record. The password field is a hash but I still don't want to return it.

I have several options that will work, but none seems particularly good:

  1. Create a custom class method findWithoutPassword for the User model and within that method do a User.find with the attributes set as shown in the Sequelize docs

  2. Do a normal User.find and filter the results in the controller (not preferred)

  3. Use some other library to strip off unwanted attributes

Is there a better way? Best of all would be if there is a way to specify in the Sequelize model definition to never return the password field, but I haven't found a way to do that.

8

8 Answers

108
votes

Another way is to add a default scope to the User model.

Add this in the model's options object

defaultScope: {
  attributes: { exclude: ['password'] },
}

Or you can create a separate scope to use it only in certain queries.

Add this in the model's options object

scopes: {
  withoutPassword: {
    attributes: { exclude: ['password'] },
  }
}

Then you can use it in queries

User.scope('withoutPassword').findAll();
75
votes

I would suggest overriding the toJSON function:

sequelize.define('user', attributes, {
  instanceMethods: {
    toJSON: function () {
      var values = Object.assign({}, this.get());

      delete values.password;
      return values;
    }
  }
});

Or in sequelize v4

const User = sequelize.define('user', attributes, {});

User.prototype.toJSON =  function () {
  var values = Object.assign({}, this.get());

  delete values.password;
  return values;
}

toJSON is called when the data is returned to the user, so end users won't see the password field, but it will still be available in your code.

Object.assign clones the returned object - Otherwise you will completely delete the property from the instance.

33
votes

I like to use a combination of both of Pawan's answers and declare the following:

defaultScope: {
    attributes: { exclude: ['password'] },
},
scopes: {
    withPassword: {
        attributes: { },
    }
}

This allows me to exclude the password by default and use the withPassword scope to explicitly return the password when needed, such as when running a login method.

userModel.scope('withPassword').findAll()

This ensure that the password is not returned when including the user via a referenced field, e.g.

accountModel.findAll({
    include: [{
        model: userModel,
        as: 'user'
    }]
})
30
votes

Maybe you can just add exclude at your attribute when you find, look like this:

var User = sequelize.define('user', attributes);

User.findAll({
    attributes: {
        exclude: ['password']
    }
});

Read the docs for more details

4
votes

I was able to get this working by adding a getter to the field which returns undefined

firstName: {
      type: DataTypes.STRING,
      get() {
        return undefined;
      }
    }

The accepted answer does not work when the model is included from another model.

I had a virtual field, say fullName that depends on the fields that I wanted to hide, say firstName and lastName. And the solution based on defaultScope does not work in this case.

3
votes

The code below worked for me. We wanted access to the instance attributes at runtime but remove them before sending the data to the client.

const Sequelize = require('sequelize')

const sequelize = new Sequelize('postgres://user:[email protected]:5432/dbname')

const PROTECTED_ATTRIBUTES = ['password', 'token']

const Model = Sequelize.Model

class User extends Model {
  toJSON () {
    // hide protected fields
    let attributes = Object.assign({}, this.get())
    for (let a of PROTECTED_ATTRIBUTES) {
      delete attributes[a]
    }
    return attributes
  }
}

User.init({
  email: {
    type: Sequelize.STRING,
    unique: true,
    allowNull: false,
    validate: {
      isEmail: true
    }
  },
  password: {
    type: Sequelize.STRING,
    allowNull: false
  },
  token: {
    type: Sequelize.STRING(16),
    unique: true,
    allowNull: false
  },
},
{
  sequelize,
  modelName: 'user'
})

module.exports = User

Github Gist

0
votes

there's a plugin for scoping attributes as discussed here.

i went with the override as mentioned in the accepted answer, except i called the original toJSON rather than get with this.constructor.super_.prototype.toJSON.apply(this, arguments) as described in the api docs

0
votes

You can do this in a simple way by excluding attributes. See the below code (comment line)

    Patient.findByPk(id, {
            attributes: {
              exclude: ['UserId', 'DiseaseId'] // Removing UserId and DiseaseId from Patient response data
            },
            include: [
              { 
                model: models.Disease
              },
              {
                model: models.User,
                attributes: {
                  exclude: ['password'] // Removing password from User response data
                }
              }
           ]
    })
    .then(data => {
        res.status(200).send(data);
    })
    .catch(err => {
        res.status(500).send({
            message: `Error retrieving Patient with id ${id} : ${err}`
        });
    });