Ruby on Rails / Devise - Requiring password on email change
This is it, for all the marbles, if I can get this issue solved then I have a project completed.
Anyway, I am using Ruby on Rails 3 with Devise for user authentication. As you may know, in the user admin/edit by default, a user has to enter their current password in the current_password field if they provide a new password. There is a TON of information out there on how to disable current_password so users can change and save freely.
However, I can find very little on doing the opposite: requiring the current password for more fields...in my case, the email field. AND only require the current password when that email addy is changed, not if it remains the same. Currently users can freely change their email without giving their current password, and for security reasons, I don't want this.
After looking through the Devise wiki, I did find this page and I thought I could reverse this code to complete this solution. I managed to work this little bit out in my user.rb model (I stripped out of the unnecessary logic for this post)....
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :name, :email, :password, :password_confirmation, :avatar, :remember_me, :agree
attr_accessor :accessible, :agree, :signed_in
attr_readonly :name
# Validation
validates :name, :presence => TRUE, :uniqueness => TRUE, :length => { :within => 4..20 }
validates :agree, :term_agreement => TRUE, :unless => :signed_in
validates_attachment_size :avatar, :less_than => 1.megabyte
validates_attachment_content_type :avatar, :content_type => ['image/jpeg', 'image/png', 'image/gif']
validates :current_password, :presence => TRUE, :if => :password_required?
protected
def password_required?
email_changed?
end
end
It "almost" works. If I save the user profi开发者_C百科le with changing nothing, or change other non-password required field (like the user avatar), the profile saves fine, no password required. So far, so good.
And if I change the email address, the validation is triggered....but what happens is that both the current_password AND password (for new password) fields trigger as required. And if I fill in the password in all three (password, password_confirmation, current_password) just for the hell of it, it won't take, just gives a validation error again.
Basically, ONLY the current_password should be required if the email address is changed. How would I make this work in my code?
- UPDATE *******
I checked out my log in response to the below suggestion by bowsersenior, and see the following lines when I attempt to save and update the email...
User Load (0.2ms) SELECT `users`.`id` FROM `users` WHERE (LOWER(`users`.`email`) = LOWER('newaddress@changed.com')) AND (`users`.id <> 1) LIMIT 1
User Load (0.2ms) SELECT `users`.`id` FROM `users` WHERE (`users`.`name` = BINARY 'Administrator') AND (`users`.id <> 1) LIMIT 1
SQL (0.1ms) ROLLBACK
I wonder if that 'ROLLBACK' has something to do with the final issue?
Give this a try:
validates :current_password, :presence => TRUE, :if => :email_changed?
I strongly suggest you leave password_required?
alone. That could lead to bugs with security and unstable behavior.
I believe the Devise way of doing this is as follows:
class RegistrationsController < Devise::RegistrationsController
def update
@user = User.find(current_user.id)
successfully_updated = if needs_password?(@user, params)
@user.update_with_password(params[:user])
else
# remove the virtual current_password attribute update_without_password
# doesn't know how to ignore it
params[:user].delete(:current_password)
@user.update_without_password(params[:user])
end
if successfully_updated
set_flash_message :notice, :updated
redirect_to after_update_path_for(@user)
else
render "edit"
end
end
private
def needs_password?(user, params)
user.email != params[:user][:email]
end
end
In other words, everything happens at the Controller level and we use either User#update_with_password
, when user want to change his email (we know that by invoking needs_password?
in the update action) or User#update_without_password
, when user changes his other profile data.
Also remember you need to "register" this new RegistrationsController in your routes:
devise_for :users, :controllers => { :registrations => "registrations" }
Source:
- https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
Similar to Pawel's answer, but rather than override the controller method update, how about this, based on the devise wiki
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
if resource.email != params[:email] || params[:password].present?
super
else
params.delete(:current_password)
resource.update_without_password(params)
end
end
end
精彩评论