为什么添加密码恢复后密码验证不起作用?

Why password validation does not work after adding password recovery?

遇到了一个我不清楚的问题。 我在注册时使用了密码验证。

def pass_val
    if password_digest.count("a-z") <= 0 || password_digest.count("A-Z") <= 0 
      errors.add(:password, "must contain 1 small letter, 1 capital letter and minimum 8 symbols")
    end
  end
validates :password_digest, presence: true,
                            length: { minimum: 8 }

我可以为用户恢复密码,验证只是停止工作。 我将验证中的所有 password_digest 更改为 password 并且验证再次起作用。 但是,之后,密码恢复消失了,开始诅咒验证。

NoMethodError in PasswordResetsController#create
undefined method `count' for nil:NilClass

如果我再次将所有 password 更改为 password_digest,验证将消失,但恢复密码将再次起作用。

更新

  1. 使用 password - 在控制台中验证有效(在网站上也有效。密码恢复无效)。
  2. 使用 password_digest - 控制台中的验证也有效(在网站上无效,恢复密码有效)。

更新 2 此外,在将信件发送到邮件(发送前)时,密码恢复过程中会出错,而不是在密码更改过程中。

更新 3 在 PasswordResetsController 中创建方法:

def create
  author = Author.find_by_email(params[:email])
  author.send_password_reset if author
  flash[:success] = "Email sent with password reset instructions."
  redirect_to root_path
end

更新 4 发送 password_reset:

  def send_password_reset
    confirmation_token
    self.password_reset_sent_at = Time.zone.now
    save!
    AuthorMailer.password_reset(self).deliver!
  end

但是信件没有发送,崩溃到此为止。 我想强调我要么在验证中使用password_digest,但验证不起作用,密码恢复有效。或者我在验证和验证工作中使用 password,但密码恢复没有。

另外值得注意的是,PasswordResetsController中的create方法并不是用来修改密码的,而是用来发邮件到邮箱的。搞不懂validation为什么骂他

错误在 send_password_reset 方法中。 向此方法添加了 (: validate => false),它起作用了。

  def send_password_reset
    confirmation_token
    self.password_reset_sent_at = Time.zone.now
    save!(:validate => false)
    AuthorMailer.password_reset(self).deliver!
  end

在验证中我使用 password

看来您自己已经找到了解决办法。但是我不确定你是否理解为什么会发生这种情况。

让我们从 password_digest 不包含密码而是密码的散列版本这一重要事实开始。 (散列函数的结果也称为摘要。)您需要将验证添加到 password 属性,因为它包含实际密码。

当您为作者分配新密码时,密码和密码摘要都将被分配一个新值。您可以想象 password setter 看起来像这样:

def password=(password)
  @password = password
  @password_digest = do_some_hash_magic(password)
end

因为 password 是在您将它分配给作者时设置的,所以您可以验证它的内容。

另一件需要知道的重要事情是,实际密码永远不会保存到数据库中,只有密码摘要(出于安全原因)。出于这个原因,当您(重新)从数据库加载实例时 password 设置为 nil,因为应用程序不再知道它是什么。

我建议使用 :allow_nil option or making the validation conditional 而不是跳过 save(validate: false) 的验证,以便仅在 password 不是 nil 时触发验证。

validates :password, length: { in: 8..255 }, allow_nil: true

validates :password, format: { with: /\p{Lower}/,    message: :no_lower   }, allow_nil: true
validates :password, format: { with: /\p{Upper}/,    message: :no_upper   }, allow_nil: true
validates :password, format: { with: /\p{Digit}/,    message: :no_digit   }, allow_nil: true
validates :password, format: { with: /[\p{S}\p{P}]/, message: :no_special }, allow_nil: true

或者,您可以更明确地使用带有自定义上下文的 :on 选项,以仅在您希望它们触发时触发验证:

validates :password, length: { in: 8..255 }, on: :password

validates :password, format: { with: /\p{Lower}/,    message: :no_lower   }, on: :password
validates :password, format: { with: /\p{Upper}/,    message: :no_upper   }, on: :password
validates :password, format: { with: /\p{Digit}/,    message: :no_digit   }, on: :password
validates :password, format: { with: /[\p{S}\p{P}]/, message: :no_special }, on: :password

上下文不必以属性命名,使用 :foo 而不是 :password 也可以。然后,您可以通过在调用 valid?save.

时传递上下文来 运行 这些验证
author.valid?(:password)
author.save(context: :password)

如果您使用 Rails 5 或更高版本,并且想要 运行 混合验证,则可以将多个上下文作为数组传递。 author.valid?([:create, :password])

有关所用正则表达式的更多信息,请查看 Ruby regexp documentation