DAVID RICE

Software Developer

+44 (0) 7590 538 303

21 Ormeau Avenue
Belfast, Northern Ireland
BT2 8HD

Resetting passwords with acts_as_authenticated

14 May 2006 {View Comments}

acts_as_authenticated is a great plugin for ruby on rails that handles a lot of the common user account functions you might find yourself developing, time and time again. The great thing is that a couple of commands and everything is generated in your app so it’s easy to modify should you need.

Some recent work i’ve been doing needed a forgotten passwords system, when I went over to the acts_as_authenticated wiki to find the Password Resetting page blank I knew it might be nice to share things when i’d finished.

First of all, i’m assuming that you’ve actually setup acts_as_authenticated and the user activation / mailer extras

It’s a little bit of work to get everything setup, but following on from the practices displayed in the other two enhancements, things should be pretty familiar.

Migration

First we’ll generate a migration to add the password reset code field, that we’ll use to ensure that our user has indeed requested to change their password

class AddPasswordResetCode < ActiveRecord::Migration
  def self.up
    add_column "users", "password_reset_code", :string, :limit => 40
  end

  def self.down
    remove_column "users", "password_reset_code"
  end
end

Model ( user.rb )

Okay, lets set up a couple more methods in our User model

def forgot_password
  @forgotten_password = true
  self.make_password_reset_code
end

def reset_password
  @reset_password = true
  update_attributes(:password_reset_code => nil)
end

def recently_reset_password?
  @reset_password
end

def recently_forgot_password?
  @forgotten_password
end

protected

def make_password_reset_code
  self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split('//').sort_by {rand}.join )
end

Controller ( account_controller.rb )

Now the two methods for requesting to change the password, and then reseting it to the users choice

def forgot_password
  return unless request.post?
  @user = User.find_by_email(params[:email])
  @user.forgot_password
  if @user and @user.save
    redirect_back_or_default(:controller => '/account', :action => 'index')
    flash[:notice] = "A Password reset link has been sent to your email address"
  else
    flash[:notice] = "Could not find a user with that email address"
  end
end

def reset_password
  @user = User.find_by_password_reset_code(params[:id])
  return if @user unless params[:password]
    if (params[:password] == params[:password_confirmation])
      current_user.password_confirmation = params[:password_confirmation]
      current_user.password = params[:password]
      @user.reset_password
      flash[:notice] = current_user.save ? "Password reset" : "Password not reset" 
    else
      flash[:notice] = "Password mismatch" 
    end  
    redirect_back_or_default(:controller => '/account', :action => 'index') 
end

Views

Add these two new views to views/accounts

reset_password.rhtml

<%= start_form_tag %>
<p><label for="password">Password</label><br/>
<%= password_field_tag 'password' %></p>

<p><label for="password_confirmation">Confirm Password</label><br/>
<%= password_field_tag 'password_confirmation' %></p>

<p><%= submit_tag 'Reset password' %></p>
<%= end_form_tag %>

forgot_password.rhtml

<%= start_form_tag %> 
<p><label for="email">Email Address</label><br/>
<%= text_field_tag 'email' %></p>

<p><%= submit_tag 'Forgot password' %></p>
<%= end_form_tag %>

user_notifier.rb

Add these two new methods

def forgot_password(user)
  setup_email(user)
  @subject    += 'Request to change your password'
  @body[:url]  = "http://localhost:3000/account/reset_password/#{user.password_reset_code}"
end

def reset_password(user)
  setup_email(user)
  @subject    += 'Your password has been reset'
end

user_observer

Add two new lines to the after_save function

def after_save(user)
  ...
  UserNotifier.deliver_forgot_password(user) if user.recently_forgot_password?
  UserNotifier.deliver_reset_password(user) if user.recently_reset_password?
end

Mailer Templates

Add these two new templates to views/user_notifier

forgot_password.rhtml

<%= @user.login %>, follow the link to reset your password

<%= @url %>

reset_password.rhtml

<%= @user.login %>, Your password has been reset
«