merb-auth-slice-password-reset 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +15 -0
- data/LICENSE +20 -0
- data/README.textile +219 -0
- data/Rakefile +41 -0
- data/TODO +2 -0
- data/app/controllers/application.rb +5 -0
- data/app/controllers/passwords.rb +49 -0
- data/app/helpers/application_helper.rb +64 -0
- data/app/helpers/mailer_helper.rb +28 -0
- data/app/mailers/password_reset_mailer.rb +13 -0
- data/app/mailers/views/password_reset_mailer/password_reset.text.erb +3 -0
- data/app/views/layout/merb_auth_slice_password_reset.html.erb +16 -0
- data/app/views/passwords/forgot_password.html.erb +7 -0
- data/app/views/passwords/reset.html.erb +15 -0
- data/config/init.rb +80 -0
- data/lib/merb-auth-slice-password-reset.rb +82 -0
- data/lib/merb-auth-slice-password-reset/merbtasks.rb +112 -0
- data/lib/merb-auth-slice-password-reset/mixins/senile_user.rb +81 -0
- data/lib/merb-auth-slice-password-reset/mixins/senile_user/ar_senile_user.rb +19 -0
- data/lib/merb-auth-slice-password-reset/mixins/senile_user/dm_senile_user.rb +22 -0
- data/lib/merb-auth-slice-password-reset/mixins/senile_user/mm_senile_user.rb +22 -0
- data/lib/merb-auth-slice-password-reset/mixins/senile_user/sq_senile_user.rb +20 -0
- data/lib/merb-auth-slice-password-reset/slicetasks.rb +18 -0
- data/lib/merb-auth-slice-password-reset/spectasks.rb +75 -0
- data/public/javascripts/master.js +0 -0
- data/public/stylesheets/master.css +2 -0
- data/spec/mailers/password_reset_mailer_spec.rb +50 -0
- data/spec/mixins/senile_user_spec.rb +111 -0
- data/spec/requests/passwords_spec.rb +85 -0
- data/spec/spec_helper.rb +61 -0
- data/stubs/app/controllers/passwords.rb +13 -0
- data/stubs/app/mailers/views/password_reset_mailer/new_password.html.erb +3 -0
- data/stubs/app/mailers/views/password_reset_mailer/password_reset.text.erb +5 -0
- data/stubs/app/views/passwords/forgot_password.html.erb +7 -0
- metadata +169 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Merb
|
2
|
+
class Authentication
|
3
|
+
module Mixins
|
4
|
+
module SenileUser
|
5
|
+
module MMClassMethods
|
6
|
+
def self.extended(base)
|
7
|
+
base.class_eval do
|
8
|
+
key :password_reset_code, String
|
9
|
+
end # base.class_eval
|
10
|
+
def find_with_password_reset_code(code)
|
11
|
+
find(:first, :conditions => {:password_reset_code => code})
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_with_login_param(param_name, value)
|
15
|
+
find(:first, :conditions => {param_name => value})
|
16
|
+
end
|
17
|
+
end # self.extended
|
18
|
+
end # MMClassMethods
|
19
|
+
end # SenileUser
|
20
|
+
end # Mixins
|
21
|
+
end # Authentication
|
22
|
+
end #Merb
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Merb
|
2
|
+
class Authentication
|
3
|
+
module Mixins
|
4
|
+
module SenileUser
|
5
|
+
module SQClassMethods
|
6
|
+
def self.extended(base)
|
7
|
+
def find_with_password_reset_code(code)
|
8
|
+
self[:password_reset_code => code]
|
9
|
+
end
|
10
|
+
def find_with_login_param(param_name, value)
|
11
|
+
self[param_name => value]
|
12
|
+
end
|
13
|
+
end # self.extended
|
14
|
+
end # SQClassMethods
|
15
|
+
module SQInstanceMethods
|
16
|
+
end # SQInstanceMethods
|
17
|
+
end # SenileUser
|
18
|
+
end # Mixins
|
19
|
+
end # Authentication
|
20
|
+
end # Merb
|
@@ -0,0 +1,18 @@
|
|
1
|
+
namespace :slices do
|
2
|
+
namespace :"merb-auth-slice-password-reset" do
|
3
|
+
|
4
|
+
# add your own merb-auth-slice-password-reset tasks here
|
5
|
+
|
6
|
+
# implement this to test for structural/code dependencies
|
7
|
+
# like certain directories or availability of other files
|
8
|
+
desc "Test for any dependencies"
|
9
|
+
task :preflight do
|
10
|
+
end
|
11
|
+
|
12
|
+
# implement this to perform any database related setup steps
|
13
|
+
desc "Migrate the database"
|
14
|
+
task :migrate do
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
namespace :slices do
|
2
|
+
namespace :"merb-auth-slice-password-reset" do
|
3
|
+
|
4
|
+
desc "Run slice specs within the host application context"
|
5
|
+
task :spec => [ "spec:explain", "spec:default" ]
|
6
|
+
|
7
|
+
namespace :spec do
|
8
|
+
|
9
|
+
slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
10
|
+
|
11
|
+
task :explain do
|
12
|
+
puts "\nNote: By running MerbAuthSlicePasswordReset specs inside the application context any\n" +
|
13
|
+
"overrides could break existing specs. This isn't always a problem,\n" +
|
14
|
+
"especially in the case of views. Use these spec tasks to check how\n" +
|
15
|
+
"well your application conforms to the original slice implementation."
|
16
|
+
end
|
17
|
+
|
18
|
+
Spec::Rake::SpecTask.new('default') do |t|
|
19
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
20
|
+
t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
|
24
|
+
Spec::Rake::SpecTask.new('model') do |t|
|
25
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
26
|
+
if(ENV['MODEL'])
|
27
|
+
t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
|
28
|
+
else
|
29
|
+
t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Run all controller specs, run a spec for a specific Controller with CONTROLLER=MyController"
|
34
|
+
Spec::Rake::SpecTask.new('controller') do |t|
|
35
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
36
|
+
if(ENV['CONTROLLER'])
|
37
|
+
t.spec_files = Dir["#{slice_root}/spec/controllers/**/#{ENV['CONTROLLER']}_spec.rb"].sort
|
38
|
+
else
|
39
|
+
t.spec_files = Dir["#{slice_root}/spec/controllers/**/*_spec.rb"].sort
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Run all view specs, run specs for a specific controller (and view) with CONTROLLER=MyController (VIEW=MyView)"
|
44
|
+
Spec::Rake::SpecTask.new('view') do |t|
|
45
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
46
|
+
if(ENV['CONTROLLER'] and ENV['VIEW'])
|
47
|
+
t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/#{ENV['VIEW']}*_spec.rb"].sort
|
48
|
+
elsif(ENV['CONTROLLER'])
|
49
|
+
t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/*_spec.rb"].sort
|
50
|
+
else
|
51
|
+
t.spec_files = Dir["#{slice_root}/spec/views/**/*_spec.rb"].sort
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Run all mailer specs, run a spec for a specific Mailer with MAILER=MyMailer"
|
56
|
+
Spec::Rake::SpecTask.new('mailer') do |t|
|
57
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
58
|
+
if(ENV['MAILER'])
|
59
|
+
t.spec_files = Dir["#{slice_root}/spec/mailer/**/#{ENV['MAILER']}_spec.rb"].sort
|
60
|
+
else
|
61
|
+
t.spec_files = Dir["#{slice_root}/spec/mailers/**/*_spec.rb"].sort
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "Run all specs and output the result in html"
|
66
|
+
Spec::Rake::SpecTask.new('html') do |t|
|
67
|
+
t.spec_opts = ["--format", "html"]
|
68
|
+
t.libs = ['lib', 'server/lib' ]
|
69
|
+
t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe "PasswordResetMailer" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
Merb::Router.prepare { add_slice(:merb_auth_slice_password_reset)}
|
7
|
+
User.auto_migrate!
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
Merb::Router.reset!
|
12
|
+
end
|
13
|
+
|
14
|
+
describe MerbAuthSlicePasswordReset::PasswordResetMailer do
|
15
|
+
|
16
|
+
def deliver(action, mail_opts= {},opts = {})
|
17
|
+
MerbAuthSlicePasswordReset::PasswordResetMailer.dispatch_and_deliver action, mail_opts, opts
|
18
|
+
@delivery = Merb::Mailer.deliveries.last
|
19
|
+
end
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@u = User.new(:email => "homer@simpsons.com", :login => "homer")
|
23
|
+
@u.send(:password_reset_code=, "12345")
|
24
|
+
@mailer_params = { :from => "info@mysite.com", :to => @u.email, :subject => "Welcome to MySite.com" }
|
25
|
+
end
|
26
|
+
|
27
|
+
after(:each) do
|
28
|
+
Merb::Mailer.deliveries.clear
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should send mail to homer@simpsons.com for the password reset email" do
|
32
|
+
deliver(:password_reset, @mailer_params, :user => @u)
|
33
|
+
@delivery.assigns(:headers).should include("to: homer@simpsons.com")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should send the mail from 'info@mysite.com' for the the password reset email" do
|
37
|
+
deliver(:password_reset, @mailer_params, :user => @u)
|
38
|
+
@delivery.assigns(:headers).should include("from: info@mysite.com")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should mention the password reset link in the the password reset emails" do
|
42
|
+
deliver(:password_reset, @mailer_params, :user => @u)
|
43
|
+
the_url = MerbAuthSlicePasswordReset::PasswordResetMailer.new.slice_url(:reset_password, :password_reset_code => @u.password_reset_code)
|
44
|
+
the_url.should_not be_nil
|
45
|
+
@delivery.text.should include(the_url)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe "Senile User" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
Merb::Router.prepare { add_slice(:merb_auth_slice_password_reset) }
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
Merb::Router.reset!
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "SenileUser Mixin" do
|
14
|
+
|
15
|
+
include SenileUserSpecHelper
|
16
|
+
|
17
|
+
before(:all) do
|
18
|
+
User.auto_migrate!
|
19
|
+
end
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@user = User.new(user_attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:each) do
|
26
|
+
User.all.destroy!
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should add the 'password_reset_code' property to the user model" do
|
30
|
+
@user.should respond_to(:password_reset_code)
|
31
|
+
@user.should respond_to(:password_reset_code=)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# describe "SenileUser Mixin Activation Code" do
|
38
|
+
#
|
39
|
+
# include SenileUserSpecHelper
|
40
|
+
#
|
41
|
+
# before(:all) do
|
42
|
+
# User.auto_migrate!
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# after(:each) do
|
46
|
+
# User.all.destroy!
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# before(:each) do
|
50
|
+
# @user = User.new(user_attributes)
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# it "should set the activation_code" do
|
54
|
+
# @user.activation_code.should be_nil
|
55
|
+
# @user.save
|
56
|
+
# @user.activation_code.should_not be_nil
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
#
|
61
|
+
# describe "Activation" do
|
62
|
+
#
|
63
|
+
# include SenileUserSpecHelper
|
64
|
+
#
|
65
|
+
# before(:all) do
|
66
|
+
# User.auto_migrate!
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# before(:each) do
|
70
|
+
# User.all.destroy!
|
71
|
+
# @user = User.create(user_attributes)
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# after(:each) do
|
75
|
+
# User.all.destroy!
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# it "should mark users as active" do
|
79
|
+
# @user.should_not be_active
|
80
|
+
# @user.activate
|
81
|
+
# @user.should be_active
|
82
|
+
# @user.reload
|
83
|
+
# @user.should be_active
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# it "should mark user as (just) activated" do
|
87
|
+
# @user.activate
|
88
|
+
# @user.should be_recently_activated
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# it "should set the activated_at property to the current date and time" do
|
92
|
+
# now = DateTime.now
|
93
|
+
# DateTime.should_receive(:now).and_return(now)
|
94
|
+
# @user.activate
|
95
|
+
# @user.activated_at.should == now
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# it "should clear out the activation code" do
|
99
|
+
# @user.activation_code.should_not be_nil
|
100
|
+
# @user.activate
|
101
|
+
# @user.activation_code.should be_nil
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# it "should send out the activation notification" do
|
105
|
+
# @user.should_receive(:send_activation_notification)
|
106
|
+
# @user.activate
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
2
|
+
|
3
|
+
describe "passwords" do
|
4
|
+
# It seems I can't spec requests unless I add an application.html.erb file.
|
5
|
+
# So the following two lines and the associated code in the before and after
|
6
|
+
# blocks create and delete that file.
|
7
|
+
LAYOUT_FILE = Merb.root / "app/views/layout/application.html.erb"
|
8
|
+
@layout_file_exits = false
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
Merb::Router.reset!
|
12
|
+
Merb::Router.prepare { slice(:merb_auth_slice_password_reset) }
|
13
|
+
User.auto_migrate!
|
14
|
+
|
15
|
+
@u = User.create(:email => "homer@simpsons.com", :login => "homer")
|
16
|
+
@u.generate_password_reset_code
|
17
|
+
@code = @u.password_reset_code
|
18
|
+
|
19
|
+
if File.exists?(LAYOUT_FILE)
|
20
|
+
@layout_file_exits = true
|
21
|
+
else
|
22
|
+
File.open(LAYOUT_FILE, 'w') {|f| f.write("<%= catch_content :for_layout %>") }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
after(:all) do
|
27
|
+
Merb::Router.reset!
|
28
|
+
|
29
|
+
File.delete(LAYOUT_FILE) unless @layout_file_exits
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "reset" do
|
33
|
+
|
34
|
+
before(:each) do
|
35
|
+
@response = request("/reset_password/#{@code}")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should respond successfully" do
|
39
|
+
@response.should be_successful
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return a form to the user" do
|
43
|
+
@response.should have_xpath("//form")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should send the completed form params to /reset_check" do
|
47
|
+
@response.should have_xpath("//form[@action='/reset_check/#{@code}']")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should have password and password_confirmation attributes wrapped in a user object" do
|
51
|
+
@response.should have_xpath("//input[@name='user[password]']")
|
52
|
+
@response.should have_xpath("//input[@name='user[password_confirmation]']")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should raise a 404 if the code doesn't match a user" do
|
56
|
+
request("/reset_password/nonsense").status.should == 404
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "reset_check" do
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
@response = request("/reset_check/#{@code}", :method => "POST",
|
65
|
+
:params => { :user => { :password => :blah, :password_confirmation => :blah } } )
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should raise a 404 if the code doesn't match a user" do
|
69
|
+
request("/reset_check/nonsense").status.should == 404
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should redirect to root if the user is saved" do
|
73
|
+
@response.should redirect_to("/", :message => {:notice => "Your password has been changed"})
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should render the reset form if the user can't be saved" do
|
77
|
+
class User; def update(val); return false; end; end
|
78
|
+
response = request("/reset_check/#{@code}", :method => "POST",
|
79
|
+
:params => { :user => { :password => :blah, :password_confirmation => :blah } } )
|
80
|
+
response.should have_xpath("//form[@action='/reset_check/#{@code}']")
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'merb-core'
|
3
|
+
require 'merb-slices'
|
4
|
+
require 'spec'
|
5
|
+
require 'dm-core'
|
6
|
+
require 'dm-validations'
|
7
|
+
|
8
|
+
# Add merb-auth-slice-password-reset.rb to the search path
|
9
|
+
Merb::Plugins.config[:merb_slices][:auto_register] = true
|
10
|
+
Merb::Plugins.config[:merb_slices][:search_path] = File.join(File.dirname(__FILE__), '..', 'lib', 'merb-auth-slice-password-reset.rb')
|
11
|
+
|
12
|
+
# Using Merb.root below makes sure that the correct root is set for
|
13
|
+
# - testing standalone, without being installed as a gem and no host application
|
14
|
+
# - testing from within the host application; its root will be used
|
15
|
+
Merb.start_environment(
|
16
|
+
:testing => true,
|
17
|
+
:adapter => 'runner',
|
18
|
+
:environment => ENV['MERB_ENV'] || 'test',
|
19
|
+
:merb_root => Merb.root,
|
20
|
+
:session_store => :memory,
|
21
|
+
:exception_details => true
|
22
|
+
)
|
23
|
+
|
24
|
+
module Merb
|
25
|
+
module Test
|
26
|
+
module SliceHelper
|
27
|
+
|
28
|
+
# The absolute path to the current slice
|
29
|
+
def current_slice_root
|
30
|
+
@current_slice_root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Whether the specs are being run from a host application or standalone
|
34
|
+
def standalone?
|
35
|
+
Merb.root == ::MerbAuthSlicePasswordReset.root
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module SenileUserSpecHelper
|
43
|
+
def user_attributes(options = {})
|
44
|
+
{ :login => 'fred',
|
45
|
+
:email => 'fred@example.com'
|
46
|
+
}.merge(options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Merb::Mailer
|
51
|
+
self.delivery_method = :test_send
|
52
|
+
end
|
53
|
+
|
54
|
+
Spec::Runner.configure do |config|
|
55
|
+
config.include(Merb::Test::ViewHelper)
|
56
|
+
config.include(Merb::Test::RouteHelper)
|
57
|
+
config.include(Merb::Test::ControllerHelper)
|
58
|
+
config.include(Merb::Test::SliceHelper)
|
59
|
+
config.include(SenileUserSpecHelper)
|
60
|
+
config.before(:all){ User.auto_migrate! }
|
61
|
+
end
|