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

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

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

 # 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 

但是,此方法的问题在于,如果仅当前密码不正确,则会丢弃对用户模型的所有更改。 我想保存对用户模型的所有更改,但如果当前密码不正确,则不更改密码。 我试过像这样拆分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.") 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字段是我的User模型中的虚拟属性:

 attr_accessor :current_password 

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

谢谢!


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

在用户模型中(user.rb)

 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 

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

 # 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 

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

 class User < ActiveRecord::Base has_secure_password validate :current_password_is_correct, if: :validate_password?, on: :update def current_password_is_correct # For some stupid reason authenticate always returns false when called on self if User.find(id).authenticate(current_password) == false errors.add(:current_password, "is incorrect.") end end def validate_password? !password.blank? end attr_accessor :current_password end 

因此,从用户角度考虑,如果有人输入了错误的密码,你不希望其他东西也不会改变吗? 通常人们会在密码更新的地方发送电子邮件和密码。 如果当前密码不正确,则不要更新任何内容。

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

 if not_authenticated_correctly params = params_minus_password_stuff (or use slice, delete, etc) end #Normal update user logic 

另一种方法是使用自定义validation器,而不是在模型中嵌入此validation。 您可以在app / validators中存储这些自定义validation器,它们将由Rails自动加载。 我称之为password_match_validator.rb。

除了可重用之外,此策略还消除了在进行身份validation时重新查询User的需要,因为User实例通过rails自动传递给validation器作为“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 

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

 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添加到每个模型,你可以将它与一个问题结合起来,但这可能有点过分。 如果您有管理员与用户的单独模型,则效果很好。 请注意,validation器上使用的文件名,类名和密钥都必​​须匹配。