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.
Files changed (35) hide show
  1. data/.gitignore +15 -0
  2. data/LICENSE +20 -0
  3. data/README.textile +219 -0
  4. data/Rakefile +41 -0
  5. data/TODO +2 -0
  6. data/app/controllers/application.rb +5 -0
  7. data/app/controllers/passwords.rb +49 -0
  8. data/app/helpers/application_helper.rb +64 -0
  9. data/app/helpers/mailer_helper.rb +28 -0
  10. data/app/mailers/password_reset_mailer.rb +13 -0
  11. data/app/mailers/views/password_reset_mailer/password_reset.text.erb +3 -0
  12. data/app/views/layout/merb_auth_slice_password_reset.html.erb +16 -0
  13. data/app/views/passwords/forgot_password.html.erb +7 -0
  14. data/app/views/passwords/reset.html.erb +15 -0
  15. data/config/init.rb +80 -0
  16. data/lib/merb-auth-slice-password-reset.rb +82 -0
  17. data/lib/merb-auth-slice-password-reset/merbtasks.rb +112 -0
  18. data/lib/merb-auth-slice-password-reset/mixins/senile_user.rb +81 -0
  19. data/lib/merb-auth-slice-password-reset/mixins/senile_user/ar_senile_user.rb +19 -0
  20. data/lib/merb-auth-slice-password-reset/mixins/senile_user/dm_senile_user.rb +22 -0
  21. data/lib/merb-auth-slice-password-reset/mixins/senile_user/mm_senile_user.rb +22 -0
  22. data/lib/merb-auth-slice-password-reset/mixins/senile_user/sq_senile_user.rb +20 -0
  23. data/lib/merb-auth-slice-password-reset/slicetasks.rb +18 -0
  24. data/lib/merb-auth-slice-password-reset/spectasks.rb +75 -0
  25. data/public/javascripts/master.js +0 -0
  26. data/public/stylesheets/master.css +2 -0
  27. data/spec/mailers/password_reset_mailer_spec.rb +50 -0
  28. data/spec/mixins/senile_user_spec.rb +111 -0
  29. data/spec/requests/passwords_spec.rb +85 -0
  30. data/spec/spec_helper.rb +61 -0
  31. data/stubs/app/controllers/passwords.rb +13 -0
  32. data/stubs/app/mailers/views/password_reset_mailer/new_password.html.erb +3 -0
  33. data/stubs/app/mailers/views/password_reset_mailer/password_reset.text.erb +5 -0
  34. data/stubs/app/views/passwords/forgot_password.html.erb +7 -0
  35. metadata +169 -0
@@ -0,0 +1,13 @@
1
+ class MerbAuthSlicePasswordReset::PasswordResetMailer < Merb::MailController
2
+
3
+ include Merb::MerbAuthSlicePasswordReset::MailerHelper
4
+
5
+ controller_for_slice MerbAuthSlicePasswordReset, :templates_for => :mailer, :path => "views"
6
+
7
+ def password_reset
8
+ @user = params[:user]
9
+ Merb.logger.info "Sending Password Reset to #{@user.email} with code #{@user.password_reset_code}"
10
+ render_mail :layout => nil
11
+ end
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+ To reset your password, please visit:
2
+
3
+ <%= password_reset_url(@user) %>
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5
+ <title>Fresh MerbAuthSlicePasswordReset Slice</title>
6
+ <link href="<%= public_path_for :stylesheet, 'master.css' %>" type="text/css" charset="utf-8" rel="stylesheet" media="all" />
7
+ <script src="<%= public_path_for :javascript, 'master.js' %>" type="text/javascript" charset="utf-8"></script>
8
+ </head>
9
+ <!-- you can override this layout at slices/merb-auth-slice-password-reset/app/views/layout/merb-auth-slice-password-reset.html.erb -->
10
+ <body class="merb-auth-slice-password-reset-slice">
11
+ <div id="container">
12
+ <h1>MerbAuthSlicePasswordReset Slice</h1>
13
+ <div id="main"><%= catch_content :for_layout %></div>
14
+ </div>
15
+ </body>
16
+ </html>
@@ -0,0 +1,7 @@
1
+ <form action="" method="post">
2
+ <p>
3
+ <label for="<%= @login_param_name %>"><%= @login_param_name.to_s.capitalize.t %></label>
4
+ <input type="text" class="text" name="<%= @login_param_name %>" id="<%= @login_param_name %>" />
5
+ </p>
6
+ <input type="submit" value="Reset password" />
7
+ </form>
@@ -0,0 +1,15 @@
1
+ <h1>Please set a new password</h1>
2
+
3
+ <form action="<%= slice_url(:reset_check) %>" method="post">
4
+ <table>
5
+ <tr>
6
+ <td>Password</td>
7
+ <td><input type="password" name="user[password]" value="" id="user_password" /></td>
8
+ </tr>
9
+ <tr>
10
+ <td>Confirmation</td>
11
+ <td><input type="password" name="user[password_confirmation]" value="" id="user_password_confirmation" /></td>
12
+ </tr>
13
+ </table>
14
+ <input type="submit" value="Reset password" />
15
+ </form>
@@ -0,0 +1,80 @@
1
+ #
2
+ # ==== Standalone MerbAuthSlicePasswordReset configuration
3
+ #
4
+ # This configuration/environment file is only loaded by bin/slice, which can be
5
+ # used during development of the slice. It has no effect on this slice being
6
+ # loaded in a host application. To run your slice in standalone mode, just
7
+ # run 'slice' from its directory. The 'slice' command is very similar to
8
+ # the 'merb' command, and takes all the same options, including -i to drop
9
+ # into an irb session for example.
10
+ #
11
+ # The usual Merb configuration directives and init.rb setup methods apply,
12
+ # including use_orm and before_app_loads/after_app_loads.
13
+ #
14
+ # If you need need different configurations for different environments you can
15
+ # even create the specific environment file in config/environments/ just like
16
+ # in a regular Merb application.
17
+ #
18
+ # In fact, a slice is no different from a normal # Merb application - it only
19
+ # differs by the fact that seamlessly integrates into a so called 'host'
20
+ # application, which in turn can override or finetune the slice implementation
21
+ # code and views.
22
+ #
23
+
24
+ require(File.join(File.expand_path(File.dirname(__FILE__)),"..","lib","merb-auth-slice-password-reset"))
25
+
26
+ # Setup the required configuration for the slice
27
+ Merb::Slices::config[:merb_auth_slice_password_reset][:from_email] = "homer@example.com"
28
+ Merb::Slices::config[:merb_auth_slice_password_reset][:password_reset_host] = "example.com"
29
+
30
+
31
+ Merb::BootLoader.before_app_loads do
32
+ require "dm-core"
33
+ DataMapper.setup(:default, "sqlite3::memory:")
34
+ class User
35
+ include DataMapper::Resource
36
+ include Merb::Authentication::Mixins::SenileUser
37
+ property :id, Serial
38
+ property :email, String
39
+ property :login, String
40
+ property :password, String
41
+
42
+ def password_confirmation=(val); end;
43
+ end
44
+
45
+ class Merb::Authentication
46
+ def self.user_class
47
+ ::User
48
+ end
49
+
50
+ def store_user(user)
51
+ return nil if user.nil?
52
+ user.login
53
+ end
54
+ def fetch_user(user_id)
55
+ User.first(:login => login)
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ Merb::Config.use do |c|
62
+
63
+ # Sets up a custom session id key which is used for the session persistence
64
+ # cookie name. If not specified, defaults to '_session_id'.
65
+ # c[:session_id_key] = '_session_id'
66
+
67
+ # The session_secret_key is only required for the cookie session store.
68
+ c[:session_secret_key] = 'fe91f7d7dc909e8d5f59f8db8b4f6f866f358fae'
69
+
70
+ # There are various options here, by default Merb comes with 'cookie',
71
+ # 'memory', 'memcache' or 'container'.
72
+ # You can of course use your favorite ORM instead:
73
+ # 'datamapper', 'sequel' or 'activerecord'.
74
+ c[:session_store] = 'cookie'
75
+
76
+ # When running a slice standalone, you're usually developing it,
77
+ # so enable template reloading by default.
78
+ c[:reload_templates] = true
79
+
80
+ end
@@ -0,0 +1,82 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ $:.unshift File.dirname(__FILE__)
4
+
5
+ require(File.expand_path(File.dirname(__FILE__) / "merb-auth-slice-password-reset" / "mixins") / "senile_user")
6
+
7
+ Merb::Plugins.add_rakefiles "merb-auth-slice-password-reset/merbtasks", "merb-auth-slice-password-reset/slicetasks", "merb-auth-slice-password-reset/spectasks"
8
+
9
+ # Register the Slice for the current host application
10
+ Merb::Slices::register(__FILE__)
11
+
12
+ # Slice configuration - set this in a before_app_loads callback.
13
+ # By default a Slice uses its own layout, so you can swicht to
14
+ # the main application layout or no layout at all if needed.
15
+ #
16
+ # Configuration options:
17
+ # :layout - the layout to use; defaults to :merb-auth-slice-password-reset
18
+ # :mirror - which path component types to use on copy operations; defaults to all
19
+ Merb::Slices::config[:merb_auth_slice_password_reset][:layout] ||= :application
20
+
21
+ # All Slice code is expected to be namespaced inside a module
22
+ module MerbAuthSlicePasswordReset
23
+
24
+ # Slice metadata
25
+ self.description = "MerbAuthSlicePasswordReset is a merb slice that adds password-reset functionality for merb-auth-based merb applications."
26
+ self.version = "1.1.0"
27
+ self.author = "Daniel Neighman, Christian Kebekus"
28
+
29
+ # Stub classes loaded hook - runs before LoadClasses BootLoader
30
+ # right after a slice's classes have been loaded internally.
31
+ def self.loaded
32
+ end
33
+
34
+ # Initialization hook - runs before AfterAppLoads BootLoader
35
+ def self.init
36
+ end
37
+
38
+ # Activation hook - runs after AfterAppLoads BootLoader
39
+ def self.activate
40
+ end
41
+
42
+ # Deactivation hook - triggered by Merb::Slices.deactivate(MerbAuthSlicePasswordReset)
43
+ def self.deactivate
44
+ end
45
+
46
+ # Setup routes inside the host application
47
+ #
48
+ # @param scope<Merb::Router::Behaviour>
49
+ # Routes will be added within this scope (namespace). In fact, any
50
+ # router behaviour is a valid namespace, so you can attach
51
+ # routes at any level of your router setup.
52
+ #
53
+ # @note prefix your named routes with :merb_auth_slice_password_reset_
54
+ # to avoid potential conflicts with global named routes.
55
+ def self.setup_router(scope)
56
+ scope.match("/reset_password/:password_reset_code", :method => :get).to(:controller => "passwords", :action => "reset").name(:reset_password)
57
+ scope.match("/reset_check/:password_reset_code", :method => :post).to(:controller => "passwords", :action => "reset_check").name(:reset_check)
58
+ scope.match("/forgot_password", :method => :get).to(:controller => "passwords", :action => "forgot_password").name(:forgot_password)
59
+ scope.match("/forgot_password", :method => :post).to(:controller => "passwords", :action => "send_confirmation")
60
+ end
61
+
62
+ end
63
+
64
+ # Setup the slice layout for MerbAuthSlicePasswordReset
65
+ #
66
+ # Use MerbAuthSlicePasswordReset.push_path and MerbAuthSlicePasswordReset.push_app_path
67
+ # to set paths to merb-auth-slice-password-reset-level and app-level paths. Example:
68
+ #
69
+ # MerbAuthSlicePasswordReset.push_path(:application, MerbAuthSlicePasswordReset.root)
70
+ # MerbAuthSlicePasswordReset.push_app_path(:application, Merb.root / 'slices' / 'merb-auth-slice-password-reset')
71
+ # ...
72
+ #
73
+ # Any component path that hasn't been set will default to MerbAuthSlicePasswordReset.root
74
+ #
75
+ # Or just call setup_default_structure! to setup a basic Merb MVC structure.
76
+ MerbAuthSlicePasswordReset.setup_default_structure!
77
+ MaSPR = MerbAuthSlicePasswordReset unless defined?(MaSPR)
78
+
79
+ # Add dependencies for other MerbAuthSlicePasswordReset classes below. Example:
80
+ # dependency "merb-auth-slice-password-reset/other"
81
+
82
+ end
@@ -0,0 +1,112 @@
1
+ namespace :slices do
2
+ namespace :"merb-auth-slice-password-reset" do
3
+
4
+ desc "Install MerbAuthSlicePasswordReset"
5
+ task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
6
+
7
+ desc "Test for any dependencies"
8
+ task :preflight do # see slicetasks.rb
9
+ end
10
+
11
+ desc "Setup directories"
12
+ task :setup_directories do
13
+ puts "Creating directories for host application"
14
+ MerbAuthSlicePasswordReset.mirrored_components.each do |type|
15
+ if File.directory?(MerbAuthSlicePasswordReset.dir_for(type))
16
+ if !File.directory?(dst_path = MerbAuthSlicePasswordReset.app_dir_for(type))
17
+ relative_path = dst_path.relative_path_from(Merb.root)
18
+ puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
19
+ mkdir_p(dst_path)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ desc "Copy stub files to host application"
26
+ task :stubs do
27
+ puts "Copying stubs for MerbAuthSlicePasswordReset - resolves any collisions"
28
+ copied, preserved = MerbAuthSlicePasswordReset.mirror_stubs!
29
+ puts "- no files to copy" if copied.empty? && preserved.empty?
30
+ copied.each { |f| puts "- copied #{f}" }
31
+ preserved.each { |f| puts "! preserved override as #{f}" }
32
+ end
33
+
34
+ desc "Copy stub files and views to host application"
35
+ task :patch => [ "stubs", "freeze:views" ]
36
+
37
+ desc "Copy public assets to host application"
38
+ task :copy_assets do
39
+ puts "Copying assets for MerbAuthSlicePasswordReset - resolves any collisions"
40
+ copied, preserved = MerbAuthSlicePasswordReset.mirror_public!
41
+ puts "- no files to copy" if copied.empty? && preserved.empty?
42
+ copied.each { |f| puts "- copied #{f}" }
43
+ preserved.each { |f| puts "! preserved override as #{f}" }
44
+ end
45
+
46
+ desc "Migrate the database"
47
+ task :migrate do # see slicetasks.rb
48
+ end
49
+
50
+ desc "Freeze MerbAuthSlicePasswordReset into your app (only merb-auth-slice-password-reset/app)"
51
+ task :freeze => [ "freeze:app" ]
52
+
53
+ namespace :freeze do
54
+
55
+ desc "Freezes MerbAuthSlicePasswordReset by installing the gem into application/gems"
56
+ task :gem do
57
+ ENV["GEM"] ||= "merb-auth-slice-password-reset"
58
+ Rake::Task['slices:install_as_gem'].invoke
59
+ end
60
+
61
+ desc "Freezes MerbAuthSlicePasswordReset by copying all files from merb-auth-slice-password-reset/app to your application"
62
+ task :app do
63
+ puts "Copying all merb-auth-slice-password-reset/app files to your application - resolves any collisions"
64
+ copied, preserved = MerbAuthSlicePasswordReset.mirror_app!
65
+ puts "- no files to copy" if copied.empty? && preserved.empty?
66
+ copied.each { |f| puts "- copied #{f}" }
67
+ preserved.each { |f| puts "! preserved override as #{f}" }
68
+ end
69
+
70
+ desc "Freeze all views into your application for easy modification"
71
+ task :views do
72
+ puts "Copying all view templates to your application - resolves any collisions"
73
+ copied, preserved = MerbAuthSlicePasswordReset.mirror_files_for :view
74
+ puts "- no files to copy" if copied.empty? && preserved.empty?
75
+ copied.each { |f| puts "- copied #{f}" }
76
+ preserved.each { |f| puts "! preserved override as #{f}" }
77
+ end
78
+
79
+ desc "Freeze all mailers into your application for easy modification"
80
+ task :mailers do
81
+ puts "Copying all mailer templates to your application - resolves any collisions"
82
+ copied, preserved = MerbAuthSlicePasswordReset.mirror_files_for :mailer
83
+ puts "- no files to copy" if copied.empty? && preserved.empty?
84
+ copied.each { |f| puts "- copied #{f}" }
85
+ preserved.each { |f| puts "! preserved override as #{f}" }
86
+ end
87
+
88
+ desc "Freeze all models into your application for easy modification"
89
+ task :models do
90
+ puts "Copying all models to your application - resolves any collisions"
91
+ copied, preserved = MerbAuthSlicePasswordReset.mirror_files_for :model
92
+ puts "- no files to copy" if copied.empty? && preserved.empty?
93
+ copied.each { |f| puts "- copied #{f}" }
94
+ preserved.each { |f| puts "! preserved override as #{f}" }
95
+ end
96
+
97
+ desc "Freezes MerbAuthSlicePasswordReset as a gem and copies over merb-auth-slice-password-reset/app"
98
+ task :app_with_gem => [:gem, :app]
99
+
100
+ desc "Freezes MerbAuthSlicePasswordReset by unpacking all files into your application"
101
+ task :unpack do
102
+ puts "Unpacking MerbAuthSlicePasswordReset files to your application - resolves any collisions"
103
+ copied, preserved = MerbAuthSlicePasswordReset.unpack_slice!
104
+ puts "- no files to copy" if copied.empty? && preserved.empty?
105
+ copied.each { |f| puts "- copied #{f}" }
106
+ preserved.each { |f| puts "! preserved override as #{f}" }
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,81 @@
1
+ module Merb
2
+ class Authentication
3
+ module Mixins
4
+ # This mixin provides basic password-reset functionality for senile users.
5
+ #
6
+ # Added properties:
7
+ # :password_reset_code, String
8
+ #
9
+ # To use it simply require it and include it into your user class.
10
+ #
11
+ # class User
12
+ # include Merb::Authentication::Mixins::SenileUser
13
+ #
14
+ # end
15
+ #
16
+ module SenileUser
17
+ def self.included(base)
18
+ base.class_eval do
19
+ include Merb::Authentication::Mixins::SenileUser::InstanceMethods
20
+ extend Merb::Authentication::Mixins::SenileUser::ClassMethods
21
+
22
+ path = File.expand_path(File.dirname(__FILE__)) / "senile_user"
23
+ if defined?(DataMapper) && DataMapper::Resource > self
24
+ require path / "dm_senile_user"
25
+ extend(Merb::Authentication::Mixins::SenileUser::DMClassMethods)
26
+ elsif defined?(ActiveRecord) && ancestors.include?(ActiveRecord::Base)
27
+ require path / "ar_senile_user"
28
+ extend(Merb::Authentication::Mixins::SenileUser::ARClassMethods)
29
+ elsif defined?(Sequel) && ancestors.include?(Sequel::Model)
30
+ require path / "sq_senile_user"
31
+ extend(Merb::Authentication::Mixins::SenileUser::SQClassMethods)
32
+ elsif MongoMapper::Document > self
33
+ require path / "mm_senile_user"
34
+ extend(Merb::Authentication::Mixins::SenileUser::MMClassMethods)
35
+ end
36
+
37
+ end # base.class_eval
38
+ end # self.included
39
+
40
+ module ClassMethods
41
+ def make_key
42
+ Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
43
+ end
44
+ end # ClassMethods
45
+
46
+ module InstanceMethods
47
+ def generate_password_reset_code
48
+ pwreset_key_success = false
49
+ until pwreset_key_success
50
+ self.password_reset_code = self.class.make_key
51
+ respond_to?(:save!) ? save! : save
52
+ pwreset_key_success = self.errors.on(:password_reset_code).nil? ? true : false
53
+ end
54
+ end
55
+
56
+ def password_reset?
57
+ ! self.password_reset_code.nil?
58
+ end
59
+
60
+ # Sends out the password reset notification.
61
+ # Used 'Request to change your password' as subject if +MaSFP[:password_reset_subject]+ is not set.
62
+ def send_password_reset_notification
63
+ generate_password_reset_code
64
+ deliver_password_reset_email(:password_reset, :subject => (MaSPR[:password_reset_subject] || "Request to change your password"))
65
+ end
66
+
67
+ private
68
+
69
+ # Helper method delivering the email.
70
+ def deliver_password_reset_email(action, params)
71
+ from = MaSPR[:from_email]
72
+ raise "No :from_email option set for Merb::Slices::config[:merb_auth_slice_password_reset][:from_email]" unless from
73
+ MaSPR::PasswordResetMailer.dispatch_and_deliver(action, params.merge(:from => from, :to => self.email), :user => self)
74
+ end
75
+
76
+ end # InstanceMethods
77
+
78
+ end # SenileUser
79
+ end # Mixins
80
+ end # Authentication
81
+ end # Merb
@@ -0,0 +1,19 @@
1
+ module Merb
2
+ class Authentication
3
+ module Mixins
4
+ module SenileUser
5
+ module ARClassMethods
6
+ def self.extended(base)
7
+ def find_with_password_reset_code(code)
8
+ find(:first, :conditions => ["password_reset_code = ?", code])
9
+ end
10
+
11
+ def find_with_login_param(param_name, value)
12
+ find(:first, :conditions => ["#{param_name} = ?", value])
13
+ end
14
+ end # self.extended
15
+ end # ARClassMethods
16
+ end # SenileUser
17
+ end # Mixins
18
+ end # Authentication
19
+ end # Merb
@@ -0,0 +1,22 @@
1
+ module Merb
2
+ class Authentication
3
+ module Mixins
4
+ module SenileUser
5
+ module DMClassMethods
6
+ def self.extended(base)
7
+ base.class_eval do
8
+ property :password_reset_code, String, :writer => :protected
9
+ end # base.class_eval
10
+ def find_with_password_reset_code(code)
11
+ first(:password_reset_code => code)
12
+ end
13
+
14
+ def find_with_login_param(param_name, value)
15
+ first(param_name => value)
16
+ end
17
+ end # self.extended
18
+ end # DMClassMethods
19
+ end # SenileUser
20
+ end # Mixins
21
+ end # Authentication
22
+ end #Merb