Rails 4 - 仅在当前密码正确时才允许更改密码

时间:2015-05-18 00:00:16

标签: ruby-on-rails authentication model passwords bcrypt-ruby

在我的应用中,用户可以编辑他们的个人资料信息。在编辑配置文件表单上,用户可以更改所有字段(名称,标题等)。在同一表单上有三个字段:current_passwordpasswordpassword_confirmation。我使用bcrypt的{​​{1}}功能进行密码验证。我根本不使用Devise。

我希望用户只有在提供了正确的当前密码后才能更改密码。我之前使用了我的用户控制器的has_secure_password方法中的以下代码:

update

但是,此方法的问题在于,如果仅当前密码不正确,则会丢弃对用户模型的所有更改。我想将所有更改保存到用户模型,但如果当前密码不正确,则不更改密码。我试过像这样拆分IF语句:

# Check if the user tried changing his/her password and CANNOT be authenticated with the entered current password
if !the_params[:password].blank? && !@user.authenticate(the_params[:current_password])
  # Add an error that states the user's current password is incorrect
  @user.errors.add(:base, "Current password is incorrect.")
else    
  # Try to update the user
  if @user.update_attributes(the_params)
    # Notify the user that his/her profile was updated
    flash.now[:success] = "Your changes have been saved"
  end
end

这不起作用,因为即使当前密码不正确,用户也可以更改他/她的密码。单步执行代码时,虽然“当前密码不正确”。错误被添加到# Check if the user tried changing his/her password and CANNOT be authenticated with the entered current password if !the_params[:password].blank? && !@user.authenticate(the_params[:current_password]) # Add an error that states the user's current password is incorrect @user.errors.add(:base, "Current password is incorrect.") end # Try to update the user if @user.update_attributes(the_params) # Notify the user that his/her profile was updated flash.now[:success] = "Your changes have been saved" end ,在运行@user方法后,它似乎忽略了此错误消息。

顺便说一句,update_attributes字段是我的用户模型中的虚拟属性:

current_password

我一直在试图解决这个问题几个小时,所以我真的可以使用一些帮助。

谢谢!

解决方案

感谢papirtiger,我得到了这个工作。我从他的回答中改变了一点代码。以下是我的代码。请注意,任何代码段都可以正常工作。

在用户模型(user.rb)

attr_accessor :current_password

我的用户控制器中的代码现在只是:

class User < ActiveRecord::Base
  has_secure_password

  attr_accessor :current_password

  # Validate current password when the user is updated
  validate :current_password_is_correct, on: :update

  # Check if the inputted current password is correct when the user tries to update his/her password
  def current_password_is_correct
    # Check if the user tried changing his/her password
    if !password.blank?
      # Get a reference to the user since the "authenticate" method always returns false when calling on itself (for some reason)
      user = User.find_by_id(id)

      # Check if the user CANNOT be authenticated with the entered current password
      if (user.authenticate(current_password) == false)
        # Add an error stating that the current password is incorrect
        errors.add(:current_password, "is incorrect.")
      end
    end
  end
end

3 个答案:

答案 0 :(得分:6)

您可以在模型级别添加自定义验证,以检查密码是否已更改:

void link (struct node *n, int level)
{
  struct qnode *qn;
  struct qnode *current, *next;
  struct qnode nextd;
  // BUG 1 = nextd is uninitialized
  // BUG 2 - nextd is a stack-based variable, becomes junk when function exits
  next = &nextd; 
  // Becuase nextd is uninitialized, next->lnode points to garbage. Kaboom!
  (next->lnode)->data=5;
  cout << (next->lnode)->data << endl;

}

答案 1 :(得分:1)

从用户角度考虑,如果有人输入了错误的密码,您是否希望其他内容不会改变?通常人们会在密码更新中只有电子邮件和密码。如果当前密码不正确,则不要更新任何内容。

如果你必须这样做,那么只需移动逻辑并有两组参数或从参数中删除密码。这将是伪造的代码。

if not_authenticated_correctly
  params = params_minus_password_stuff (or use slice, delete, etc)
end

#Normal update user logic

答案 2 :(得分:1)

另一种方法是使用自定义验证器,而不是将此验证嵌入到模型中。您可以将这些自定义验证器存储在应用程序/验证器中,Rails会自动加载它们。我称这个为password_match_validator.rb。

除了可重用之外,该策略还消除了在身份验证时对User进行重新查询的需要,因为User实例通过rails作为“ record”参数自动传递给了验证器。

class PasswordMatchValidator < ActiveModel::EachValidator

   # Password Match Validator
   #
   # We need to validate the users current password
   # matches what we have on-file before we change it
   #
   def validate_each(record, attribute, value)
     unless value.present? && password_matches?(record, value)
       record.errors.add attribute, "does not match"
     end
   end

   private

   # Password Matches?
   #
   # Need to validate if the current password matches
   # based on what the password_digest was. has_secure_password
   # changes the password_digest whenever password is changed.
   #
   # @return Boolean
   #
   def password_matches?(record, value)
     BCrypt::Password.new(record.password_digest_was).is_password?(value)
   end
 end

将验证器添加到项目后,可以在任何模型中使用它,如下所示。

class User < ApplicationRecord

  has_secure_password

  # Add an accessor so you can have a field to validate
  # that is seperate from password, password_confirmation or 
  # password_digest...
  attr_accessor :current_password

  # Validation should only happen if the user is updating 
  # their password after the account has been created.
  validates :current_password, presence: true, password_match: true, on: :update, if: :password_digest_changed?

end

如果您不想将attr_accessor添加到每个模型中,则可以将其与关注点结合在一起,但这可能是过大了。如果您为管理员和用户使用单独的模型,则效果很好。请注意,文件名,类名和验证器上使用的密钥都必须匹配。