David J Rice

The blog of freelance Designer & Developer, David Rice.

14 May 2006

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
David Rice

If you need help with the Design, Build, Management, Hosting or Support of your project do get in touch, I'd love to hear from you!

Recently

Archive