【问题标题】:Sequelize: don't return password续集:不返回密码
【发布时间】:2015-01-15 20:19:21
【问题描述】:

我正在使用 Sequelize 为用户记录进行数据库查找,并且我希望模型的默认行为返回该记录的 password 字段。 password 字段是一个哈希,但我仍然不想返回它。

我有几个可行的选择,但似乎没有一个特别好:

  1. User 模型创建一个自定义类方法findWithoutPassword,并在该方法中使用attributes 设置User.find,如Sequelize docs 中所示

  2. 做一个普通的User.find并在控制器中过滤结果(不是首选)

  3. 使用其他库去除不需要的属性

有没有更好的方法?最好的办法是在 Sequelize 模型定义中指定永远不返回 password 字段,但我还没有找到这样做的方法。

【问题讨论】:

    标签: sequelize.js password-storage


    【解决方案1】:

    另一种方法是向用户模型添加默认范围。

    在模型的选项对象中添加这个

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

    或者您可以创建一个单独的范围以仅在某些查询中使用它。

    在模型的选项对象中添加这个

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

    然后你可以在查询中使用它

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

    【讨论】:

    • 重要!这是唯一对我有用的答案。重要的是要知道,仅当您直接获取用户模型时,接受的 anwser 才会起作用。如果您通过另一个模型包含用户模型,则用户模型中的 toJSON 函数将不会被调用,并且您会将密码泄露给客户端!
    • 这是最好的答案。我不知道为什么它不在顶部。
    • 注意:这个答案工作正常,但排除的字段仍将暴露给create。覆盖 toJSON 可保护字段在创建期间不被暴露。
    • @DeanKoštomaj . 在 findAll 中使用包含时,使用 toJSON 对我来说效果很好。不包括已删除的字段。
    • @DeanKoštomaj。当我尝试在孩子中包含 Parent 时注意到了这个问题。
    【解决方案2】:

    我建议重写 toJSON 函数:

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

    或在 sequelize v4 中

    const User = sequelize.define('user', attributes, {});
    
    User.prototype.toJSON =  function () {
      var values = Object.assign({}, this.get());
    
      delete values.password;
      return values;
    }
    

    toJSON 在数据返回给用户时被调用,因此最终用户不会看到密码字段,但它仍然可以在您的代码中使用。

    Object.assign 克隆返回的对象 - 否则您将从实例中完全删除该属性。

    【讨论】:

    • 重要!如果您按照上述内容进行操作,delete values.password 实际上会从用户实例中删除密码属性 - 而不仅仅是 JSON 输出。使用 var values = Object.assign({}, this.get()) 或适当的 polyfill 来避免改变实际用户的属性。
    • 使用上述代码时出错Unhandled rejection Error: TypeError: Cannot read property 'get' of undefined
    • 在这种情况下你不能这样做 - 我们使用 .bind 将上下文设置为实例 - 但你不能用箭头函数这样做
    • 如果您只是创建用户的 JSON 表示,这将起作用。但!如果您包含来自另一个模型的用户,则被调用的 toJSON 是其他模型!这将导致您在急切加载用户时泄露您的散列密码。看到这个:github.com/sequelize/sequelize/issues/3891
    • 这不再适用于 sequelize 4,instanceMethods 已被弃用,替换为这个 Model.prototype.someMethod = function () {..},根据这个 docs.sequelizejs.com/manual/tutorial/…
    【解决方案3】:

    我喜欢结合使用 Pawan 的两个答案并声明以下内容:

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

    这允许我在默认情况下排除密码,并在需要时使用withPassword 范围显式返回密码,例如在运行登录方法时。

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

    这确保在通过引用字段包含用户时不返回密码,例如

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

    【讨论】:

      【解决方案4】:

      也许你可以在find 时在属性中添加exclude,如下所示:

      var User = sequelize.define('user', attributes);
      
      User.findAll({
          attributes: {
              exclude: ['password']
          }
      });
      

      阅读docs了解更多详情

      【讨论】:

      • attributes的块添加到每个查询中并不是那么好。需要一种在模型级别定义排除属性以应用所有查询的方法!
      【解决方案5】:

      我可以通过在返回 undefined 的字段中添加一个 getter 来完成这项工作

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

      当模型包含在另一个模型中时,accepted answer 不起作用。

      我有一个虚拟字段,例如 fullName,它取决于我想要隐藏的字段,例如 firstNamelastName。而基于defaultScopesolution 在这种情况下不起作用。

      【讨论】:

      • 感谢您的提示。我挣扎了一个小时才找到需要隐藏列但在某个虚拟字段中显示的解决方案。
      【解决方案6】:

      下面的代码对我有用。我们希望在运行时访问实例属性,但在将数据发送到客户端之前将其删除。

      const Sequelize = require('sequelize')
      
      const sequelize = new Sequelize('postgres://user:pass@example.com: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

      【讨论】:

        【解决方案7】:

        scoping attributes 有一个插件,正如 here 所讨论的那样。

        我使用了接受的答案中提到的覆盖,除了我用this.constructor.super_.prototype.toJSON.apply(this, arguments) 调用原始的toJSON 而不是get,如api docs 中所述

        【讨论】:

          【解决方案8】:

          您可以通过排除属性以简单的方式执行此操作。请参阅下面的代码(注释行)

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-06-20
            • 2021-12-31
            • 2020-05-11
            • 2019-02-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多