In my app, users can edit their profile information. On the edit profile form, the user can make changes to all fields (name, title, and more). On this same form are three fields: current_password
, password
, and password_confirmation
. I am using bcrypt
's has_secure_password
feature for password authentication. I am NOT using Devise at all.
I want users to only be able to change their password if they have supplied a correct current password. I've got this working before with the following code in the update
method of my Users controller:
# 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
However, the problem with this approach is that it discards all changes to the user model if just the current password is incorrect. I want to save all changes to the user model but NOT the password change if the current password is incorrect. I've tried splitting up the IF statements like so:
# 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
This doesn't work because the user is able to change his/her password even if the current password is incorrect. When stepping through the code, although the "Current password is incorrect." error is added to @user
, after running through the update_attributes
method, it seems to ignore this error message.
By the way, the current_password
field is a virtual attribute in my User model:
attr_accessor :current_password
I've been stuck trying to figure this out for a couple of hours now, so I can really use some help.
Thanks!
Thanks to papirtiger, I got this working. I changed the code around a little bit from his answer. Below is my code. Note that either code snippet will work just fine.
In the User model (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
And the code in my Users controller is now simply:
# 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
You could add a custom validation on the model level which checks if the password has changed:
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