Parazuce
Parazuce

Reputation: 167

has_secure_password authenticate inside validation on password update

I'm using has_secure_password in a User model. I have implemented a way for users to change their password outside of the model, but to keep things DRY, I'm trying to move the validations needed from the controller to the model.

The User model looks something like this:

class User
  include Mongoid::Document
  include ActiveModel::SecurePassword

  has_secure_password

  field: :password_digest, type: String

  attr_accessible :password, :password_confirmation, :current_password
end

Users change their passwords by submitting the following:

user[current_password] - Currently stored password
user[password] - New password
user[password_confirmation] - New password confirmation

I'm using update_attributes(params[:user]) on the User model for the current user. My problem is that calling update_attributes updates the password_digest before using validations, so the following code won't work:

def password_validation_required?
  password_digest.blank? || !password.blank? || !password_confirmation.blank?
end

validate(on: :update, if: :password_validation_required?) do
  unless authenticate(current_password)
    add(:current_password, 'invalid password')
  end
end

authenticate is authenticating based on the new password_digest generated from user[password]. Is there an elegant way to access the old password_digest value for authentication? One idea I had was to re-query the user to gain access to another authenticate method that will authenticate against the old password_digest value. The problem is that it's not a clean solution.

Upvotes: 0

Views: 1625

Answers (2)

Gavin
Gavin

Reputation: 4333

I think this one's a bit cleaner than @Parazuce's:

  validate :validates_current_password

  private

  def validates_current_password
    return if password_digest_was.nil? || !password_digest_changed?
    unless BCrypt::Password.new(password_digest_was) == current_password
      errors.add(:current_password, "is incorrect")
    end
  end

Upvotes: 1

Parazuce
Parazuce

Reputation: 167

The password_digest field has ActiveModel::Dirty methods associated with it, so I decided to go with:

validate(on: :update, if: :password_validation_required?) do
  unless BCrypt::Password.new(password_digest_was) == current_password
    errors.add(:current_password, "is incorrect")
  end
end

This prevents the need to override password= with additional logic which could introduce bugs in the future if other features used password=.

Upvotes: 0

Related Questions