-
Notifications
You must be signed in to change notification settings - Fork 5.5k
How To: Allow users to edit their password
We have two options to allow users to edit their password:
- Use the registerable module, which will give you both sign up and edit user features
- Handle your own passwords controller to allow users editing their password. (see Option 2)
- Add this feature to your UsersController (see Option 3)
Example:
class PasswordsController < ApplicationController
before_filter :authenticate_user!
def edit
@user = current_user
end
def update
@user = current_user
if @user.update_with_password(params[:user])
sign_in(@user, :bypass => true)
redirect_to root_path, :notice => "Password updated!"
else
render :edit
end
end
endOverriding devise's password controller in your routes file like this:
devise_for :users, :controllers => {:passwords => "passwords"}You will also need to add the following into routes, so that Rails knows what to do (at least I did, and I had a custom registration controller-Travis):
resources :passwordsYou will then need to create the appropriate views:
Create passwords/edit.html.erb in your view and add and modify the following to your needs:
# views/passwords/edit.html.erb
<h2>Change Password</h2>
<div class="form_div">
<%= form_for(@user, :as => @user, :url => password_path, :html => { :method => :put }) do |f| %>
<p>
<%= f.label :current_password %>
<br />
<%= f.password_field :current_password %></p>
<p><%= f.label :password, "New password" %><br />
<%= f.password_field :password %></p>
<p><%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation %></p>
<p><%= f.submit "Change my password" %></p>
<%end%>
</div>It is also crucial to have attr_accessible :password, :password_confirmation (in addition to others) in you model. Otherwise, password and confirmation validation will not take place.
I have: attr_accessible :email, :remember_me, :first_name, :last_name, :address_street, :address_city, :address_state, :address_zip, :address_country, :password, :password_confirmation
If you don't want to use update_with_password, which will require to enter the current password, see How To: Allow users to edit their account without providing a password.
The disadvantage of option 2 ist that it 'abuses' the PasswordsController for something it was not meant. The original use of PasswordsController is to handle forgotten passwords and how to reset them.
Instead you could introduce a custom action to UsersController.
This example uses the inherited_resources gem, the cancan gem for authentication and Ruby 1.9 syntax. It would also work without this gems but would probably require some changes.
# /app/controllers/users_controller.rb
class UsersController < InheritedResources::Base
actions :show, :update
custom_actions resource: :change_password
load_and_authorize_resource
def update
if params[:user][:password]
if @user.update_with_password(params[:user])
sign_in(@user, bypass: true)
redirect_to user_path(@user), notice: "Password updated!"
else
render :change_password
end
else
super
end
end
endWe want to spec our new feature with a request spec using rspec and capybara.
# /spec/requests/users_spec.rb
require 'spec_helper'
describe "Users" do
let(:user) {Fabricate(:user)}
before(:each) do
login_as user
# logs in the user
# see this gist for this method: https://gist.github.com/1053489
end
describe "GET /users/12345/change_password" do
it "changes the password" do
visit change_password_user_path(user)
within("#content") do
fill_in "Current password", with: user.password
fill_in "Password", with: "8844889"
fill_in "Password confirmation", with: "8844889"
click_button "Change my password"
end
within("#content") do
page.should have_content "Password updated!"
end
end
it "fails when providing the wrong current password" do
visit change_password_user_path(user)
within("#content") do
fill_in "Current password", with: "I'm the wrong password"
fill_in "Password", with: "8844889"
fill_in "Password confirmation", with: "8844889"
click_button "Change my password"
end
within("#content") do
page.should_not have_content "Password updated!"
page.should have_content "Some errors were found, please take a look:"
page.should have_content "is invalid"
end
end
it "fails when providing different new passwords" do
visit change_password_user_path(user)
within("#content") do
fill_in "Current password", with: user.password
fill_in "Password", with: "8844889"
fill_in "Password confirmation", with: "11113333435"
click_button "Change my password"
end
within("#content") do
page.should_not have_content "Password updated!"
page.should have_content "Some errors were found, please take a look:"
page.should have_content "doesn't match confirmation"
end
end
it "fails when providing no new passwords" do
old_password = user.password
visit change_password_user_path(user)
within("#content") do
fill_in "Current password", with: user.password
click_button "Change my password"
end
within("#content") do
# This case is not really handled.
# Nothing happens. Nothing gets changed.
# But the user does not get any error message.
#page.should_not have_content "Password updated!"
user.reload
user.password.should eq(old_password)
end
end
endYou also need a /app/users/change_password.html.erb/ view. This one basically looks like the view from option 2.
The main difference is the much simpler form_for arguments list.
It is the same as for the default edit action and uses update.
I used the following one.
# /app/views/users/change_password.html.haml
- title "Change Password"
= simple_form_for(@user, :html => { :method => :put }) do |f|
= f.error_notification
.inputs
= f.input :current_password, hint: "We need your current password to confirm your changes.", required: true
= f.input :password, required: true
= f.input :password_confirmation, required: true
.actions
= f.button :submit, "Change my password"Your routes.rb needs to handle the custom action.
change your devise_forand users recources according to this snippet:
devise_for :users
resources :users, only: [:show, :edit, :update] do
member do
get :change_password
end
endThe cancan gem requires a new ability to allow the change of the password:
# /app/models/ability.rb
can :change_password, User, _id: user.id