Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kevinansfield/141137 to your computer and use it in GitHub Desktop.
Save kevinansfield/141137 to your computer and use it in GitHub Desktop.
authlogic forgotten password / password reset
From 30ea36e6455dfca9c1a6bfed23be891b69756a13 Mon Sep 17 00:00:00 2001
From: Kevin Ansfield <kev@imac-home.home>
Date: Sun, 5 Jul 2009 22:38:26 +0100
Subject: [PATCH] allow users to reset forgotten passwords
---
app/controllers/password_reset_controller.rb | 39 +++++++++
app/helpers/password_reset_helper.rb | 2 +
app/models/notifier.rb | 12 +++
app/models/user.rb | 5 +
app/views/notifier/password_reset_instructions.erb | 10 +++
app/views/password_reset/edit.html.erb | 11 +++
app/views/password_reset/new.html.erb | 8 ++
config/routes.rb | 1 +
test/functional/password_reset_controller_test.rb | 82 ++++++++++++++++++++
test/unit/helpers/password_reset_helper_test.rb | 4 +
test/unit/notifier_test.rb | 14 ++++
test/unit/user_test.rb | 8 +-
12 files changed, 193 insertions(+), 3 deletions(-)
create mode 100644 app/controllers/password_reset_controller.rb
create mode 100644 app/helpers/password_reset_helper.rb
create mode 100644 app/models/notifier.rb
create mode 100644 app/views/notifier/password_reset_instructions.erb
create mode 100644 app/views/password_reset/edit.html.erb
create mode 100644 app/views/password_reset/new.html.erb
create mode 100644 test/functional/password_reset_controller_test.rb
create mode 100644 test/unit/helpers/password_reset_helper_test.rb
create mode 100644 test/unit/notifier_test.rb
diff --git a/app/controllers/password_reset_controller.rb b/app/controllers/password_reset_controller.rb
new file mode 100644
index 0000000..4136482
--- /dev/null
+++ b/app/controllers/password_reset_controller.rb
@@ -0,0 +1,39 @@
+class PasswordResetController < ApplicationController
+ before_filter :load_user_using_perishable_token, :only => [:edit, :update]
+ before_filter :require_no_user
+
+ def update
+ @user.password = params[:user][:password]
+ @user.password_confirmation = params[:user][:password_confirmation]
+ if @user.save
+ flash[:notice] = "Password successfully updated"
+ redirect_to account_url
+ else
+ render :action => "edit"
+ end
+ end
+
+ def create
+ @user = User.find_by_email(params[:email])
+ if @user
+ @user.deliver_password_reset_instructions!
+ flash[:notice] = "Instructions to reset your password have been emailed to you. Please check your email"
+ redirect_to root_url
+ else
+ flash[:error] = "No user was found with that address"
+ render :action => :new
+ end
+ end
+
+ private
+ def load_user_using_perishable_token
+ @user = User.find_using_perishable_token(params[:id])
+ unless @user
+ flash[:error] = "We're sorry, but we could not find your account. " +
+ "If you are having issues, try copy and pasting the URL " +
+ "from your email into your browser or restarting the reset password process"
+ redirect_to root_url
+ end
+ end
+
+end
diff --git a/app/helpers/password_reset_helper.rb b/app/helpers/password_reset_helper.rb
new file mode 100644
index 0000000..c3a05db
--- /dev/null
+++ b/app/helpers/password_reset_helper.rb
@@ -0,0 +1,2 @@
+module PasswordResetHelper
+end
diff --git a/app/models/notifier.rb b/app/models/notifier.rb
new file mode 100644
index 0000000..70ed78e
--- /dev/null
+++ b/app/models/notifier.rb
@@ -0,0 +1,12 @@
+class Notifier < ActionMailer::Base
+ default_url_options[:host] = "localhost:3000"
+
+ def password_reset_instructions(user)
+ subject "Password Reset Instructions"
+ from "admin@phocial.co.uk"
+ recipients user.email
+ sent_on Time.now
+ body :edit_password_reset_url => edit_password_reset_url(user.perishable_token)
+ end
+
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index f9b65d0..e98689f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,4 +2,9 @@ class User < ActiveRecord::Base
acts_as_authentic do |c|
c.perishable_token_valid_for = 24.hours
end
+
+ def deliver_password_reset_instructions!
+ reset_perishable_token!
+ Notifier.deliver_password_reset_instructions(self)
+ end
end
diff --git a/app/views/notifier/password_reset_instructions.erb b/app/views/notifier/password_reset_instructions.erb
new file mode 100644
index 0000000..75931bc
--- /dev/null
+++ b/app/views/notifier/password_reset_instructions.erb
@@ -0,0 +1,10 @@
+A request to reset your password has been made.
+If you did not make this request, simply ignore this email.
+If you did make this request just click the link below:
+
+<%= @edit_password_reset_url %>
+
+If the above URL does not work try copying and pasting it into your browser.
+If you continue to have problems please feel free to contact us.
+
+Note that the generated URL above will only work for 24 hours for security reasons.
\ No newline at end of file
diff --git a/app/views/password_reset/edit.html.erb b/app/views/password_reset/edit.html.erb
new file mode 100644
index 0000000..b682743
--- /dev/null
+++ b/app/views/password_reset/edit.html.erb
@@ -0,0 +1,11 @@
+<% form_for @user, :url => password_reset_path, :method => :put do |f| -%>
+ <h1>Change my Password</h1>
+ <%= f.error_messages %>
+ <%= f.label :password %><br />
+ <%= f.password_field :password %><br />
+ <br />
+ <%= f.label :password_confirmation %><br />
+ <%= f.password_field :password_confirmation %><br />
+ <br />
+ <%= f.submit "Update" %>
+<% end -%>
\ No newline at end of file
diff --git a/app/views/password_reset/new.html.erb b/app/views/password_reset/new.html.erb
new file mode 100644
index 0000000..0b711da
--- /dev/null
+++ b/app/views/password_reset/new.html.erb
@@ -0,0 +1,8 @@
+<h1>Forgotten Password</h1>
+<p>Fill out the form below and instructions to reset your password will be emailed to you.</p>
+<% form_tag('/password_reset', {:class => "form"}) do %>
+ <%= label_tag :email %><br />
+ <%= text_field_tag :email %><br />
+ <br />
+ <%= submit_tag 'Submit' %>
+<% end -%>
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index c8ec928..a164ec2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -7,6 +7,7 @@ ActionController::Routing::Routes.draw do |map|
map.resource :account, :controller => 'users'
map.resources :users
map.resource :user_session
+ map.resources :password_reset
map.root :controller => 'user_sessions', :action => 'new'
diff --git a/test/functional/password_reset_controller_test.rb b/test/functional/password_reset_controller_test.rb
new file mode 100644
index 0000000..05b5306
--- /dev/null
+++ b/test/functional/password_reset_controller_test.rb
@@ -0,0 +1,82 @@
+require 'test_helper'
+
+class PasswordResetControllerTest < ActionController::TestCase
+
+ setup :activate_authlogic
+
+ test "shows new when logged out" do
+ get :new
+ assert_response :success
+ assert_select "form[action=/password_reset]" do
+ assert_select '#email'
+ end
+ end
+
+ test "redirects from new when logged in" do
+ UserSession.create(users(:bob))
+ get :new
+ assert_redirected_to account_url
+ assert_match /must be logged out/, flash[:error]
+ end
+
+ test "shows edit when logged out" do
+ get :edit, :id => users(:bob).perishable_token
+ assert_response :success
+ assert_select "form[action=/password_reset/#{users(:bob).perishable_token}]" do
+ assert_select '#user_password'
+ assert_select '#user_password_confirmation'
+ end
+ end
+
+ test "redirected from edit when logged in" do
+ UserSession.create(users(:bob))
+ get :edit, :id => users(:bob).perishable_token
+ assert_redirected_to account_url
+ assert_match /must be logged out/, flash[:error]
+ end
+
+ test "edit finds user by perishable token" do
+ get :edit, :id => users(:bob).perishable_token
+ assert assigns(:user)
+ assert_equal users(:bob), assigns(:user)
+ end
+
+ test "edit redirects with error on incorrect perishable token" do
+ get :edit, :id => "test"
+ assert_nil assigns(:user)
+ assert_redirected_to root_url
+ assert_match /could not find your account/, flash[:error]
+ end
+
+ test "create sends password reset instructions for valid user" do
+ post :create, :email => users(:bob).email
+ assert_redirected_to root_url
+ assert_not_nil flash[:notice]
+ assert_not_nil assigns(:user)
+ end
+
+ test "create shows new template if user not found" do
+ post :create, :email => "noemail@example.com"
+ assert_response :success
+ assert_template 'new'
+ assert_not_nil flash[:error]
+ end
+
+ test "updates password" do
+ old_password = "bobowns"
+ new_password = "newpassword"
+ assert users(:bob).valid_password?(old_password)
+ put :update, :id => users(:bob).perishable_token, :user => { :password => new_password, :password_confirmation => new_password }
+ assert_redirected_to account_url
+ assert_not_nil flash[:notice]
+ assert assigns(:user)
+ assert !assigns(:user).valid_password?(old_password)
+ assert assigns(:user).valid_password?(new_password)
+ end
+
+ test "update password fails without matched passwords" do
+ put :update, :id => users(:bob).perishable_token, :user => { :password => "one", :password_confirmation => "two" }
+ assert_response :success
+ assert_template 'edit'
+ end
+end
diff --git a/test/unit/helpers/password_reset_helper_test.rb b/test/unit/helpers/password_reset_helper_test.rb
new file mode 100644
index 0000000..20b95bc
--- /dev/null
+++ b/test/unit/helpers/password_reset_helper_test.rb
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class PasswordResetHelperTest < ActionView::TestCase
+end
diff --git a/test/unit/notifier_test.rb b/test/unit/notifier_test.rb
new file mode 100644
index 0000000..f208440
--- /dev/null
+++ b/test/unit/notifier_test.rb
@@ -0,0 +1,14 @@
+require 'test_helper'
+
+class NotifierTest < ActionMailer::TestCase
+ test "password reset instructions" do
+ user = users(:bob)
+
+ email = Notifier.deliver_password_reset_instructions(user)
+ assert !ActionMailer::Base.deliveries.empty?
+
+ assert_equal [user.email], email.to
+ assert_equal "Password Reset Instructions", email.subject
+ assert_match /password_reset\/#{user.perishable_token}\/edit/, email.body
+ end
+end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index a64d2d3..4c2b65a 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -1,8 +1,10 @@
require 'test_helper'
class UserTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- test "the truth" do
- assert true
+ test "delivering password reset instructions resets perishable_token" do
+ user = users(:bob)
+ old_token = user.perishable_token
+ user.deliver_password_reset_instructions!
+ assert user.perishable_token != old_token
end
end
--
1.6.3.2+GitX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment