shingara-devise 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/CHANGELOG.rdoc +119 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +253 -0
  4. data/Rakefile +45 -0
  5. data/TODO +5 -0
  6. data/app/controllers/confirmations_controller.rb +33 -0
  7. data/app/controllers/passwords_controller.rb +41 -0
  8. data/app/controllers/sessions_controller.rb +33 -0
  9. data/app/models/devise_mailer.rb +53 -0
  10. data/app/views/confirmations/new.html.erb +16 -0
  11. data/app/views/devise_mailer/confirmation_instructions.html.erb +5 -0
  12. data/app/views/devise_mailer/reset_password_instructions.html.erb +8 -0
  13. data/app/views/passwords/edit.html.erb +20 -0
  14. data/app/views/passwords/new.html.erb +16 -0
  15. data/app/views/sessions/new.html.erb +23 -0
  16. data/generators/devise/USAGE +5 -0
  17. data/generators/devise/devise_generator.rb +25 -0
  18. data/generators/devise/lib/route_devise.rb +32 -0
  19. data/generators/devise/templates/README +22 -0
  20. data/generators/devise/templates/migration.rb +20 -0
  21. data/generators/devise/templates/model.rb +5 -0
  22. data/generators/devise_install/USAGE +3 -0
  23. data/generators/devise_install/devise_install_generator.rb +9 -0
  24. data/generators/devise_install/templates/devise.rb +47 -0
  25. data/generators/devise_views/USAGE +3 -0
  26. data/generators/devise_views/devise_views_generator.rb +24 -0
  27. data/init.rb +2 -0
  28. data/lib/devise/controllers/filters.rb +111 -0
  29. data/lib/devise/controllers/helpers.rb +130 -0
  30. data/lib/devise/controllers/url_helpers.rb +49 -0
  31. data/lib/devise/encryptors/authlogic_sha512.rb +28 -0
  32. data/lib/devise/encryptors/clearance_sha1.rb +26 -0
  33. data/lib/devise/encryptors/restful_authentication_sha1.rb +29 -0
  34. data/lib/devise/encryptors/sha1.rb +34 -0
  35. data/lib/devise/encryptors/sha512.rb +34 -0
  36. data/lib/devise/failure.rb +36 -0
  37. data/lib/devise/hooks/confirmable.rb +11 -0
  38. data/lib/devise/hooks/rememberable.rb +27 -0
  39. data/lib/devise/locales/en.yml +18 -0
  40. data/lib/devise/mapping.rb +120 -0
  41. data/lib/devise/migrations.rb +57 -0
  42. data/lib/devise/models/authenticatable.rb +87 -0
  43. data/lib/devise/models/confirmable.rb +156 -0
  44. data/lib/devise/models/recoverable.rb +88 -0
  45. data/lib/devise/models/rememberable.rb +95 -0
  46. data/lib/devise/models/validatable.rb +36 -0
  47. data/lib/devise/models.rb +110 -0
  48. data/lib/devise/orm/mongo_mapper.rb +26 -0
  49. data/lib/devise/rails/routes.rb +109 -0
  50. data/lib/devise/rails/warden_compat.rb +26 -0
  51. data/lib/devise/rails.rb +17 -0
  52. data/lib/devise/strategies/authenticatable.rb +46 -0
  53. data/lib/devise/strategies/base.rb +24 -0
  54. data/lib/devise/strategies/rememberable.rb +35 -0
  55. data/lib/devise/version.rb +3 -0
  56. data/lib/devise/warden.rb +20 -0
  57. data/lib/devise.rb +130 -0
  58. data/test/controllers/filters_test.rb +103 -0
  59. data/test/controllers/helpers_test.rb +55 -0
  60. data/test/controllers/url_helpers_test.rb +47 -0
  61. data/test/devise_test.rb +72 -0
  62. data/test/encryptors_test.rb +28 -0
  63. data/test/failure_test.rb +34 -0
  64. data/test/integration/authenticatable_test.rb +195 -0
  65. data/test/integration/confirmable_test.rb +89 -0
  66. data/test/integration/recoverable_test.rb +131 -0
  67. data/test/integration/rememberable_test.rb +65 -0
  68. data/test/mailers/confirmation_instructions_test.rb +59 -0
  69. data/test/mailers/reset_password_instructions_test.rb +62 -0
  70. data/test/mapping_test.rb +101 -0
  71. data/test/models/authenticatable_test.rb +130 -0
  72. data/test/models/confirmable_test.rb +237 -0
  73. data/test/models/recoverable_test.rb +141 -0
  74. data/test/models/rememberable_test.rb +130 -0
  75. data/test/models/validatable_test.rb +99 -0
  76. data/test/models_test.rb +111 -0
  77. data/test/rails_app/app/controllers/admins_controller.rb +6 -0
  78. data/test/rails_app/app/controllers/application_controller.rb +10 -0
  79. data/test/rails_app/app/controllers/home_controller.rb +4 -0
  80. data/test/rails_app/app/controllers/users_controller.rb +7 -0
  81. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  82. data/test/rails_app/app/models/account.rb +3 -0
  83. data/test/rails_app/app/models/admin.rb +3 -0
  84. data/test/rails_app/app/models/organizer.rb +3 -0
  85. data/test/rails_app/app/models/user.rb +3 -0
  86. data/test/rails_app/config/boot.rb +110 -0
  87. data/test/rails_app/config/environment.rb +41 -0
  88. data/test/rails_app/config/environments/development.rb +17 -0
  89. data/test/rails_app/config/environments/production.rb +28 -0
  90. data/test/rails_app/config/environments/test.rb +28 -0
  91. data/test/rails_app/config/initializers/new_rails_defaults.rb +21 -0
  92. data/test/rails_app/config/initializers/session_store.rb +15 -0
  93. data/test/rails_app/config/routes.rb +18 -0
  94. data/test/routes_test.rb +79 -0
  95. data/test/support/assertions_helper.rb +22 -0
  96. data/test/support/integration_tests_helper.rb +66 -0
  97. data/test/support/model_tests_helper.rb +51 -0
  98. data/test/test_helper.rb +40 -0
  99. metadata +161 -0
@@ -0,0 +1,34 @@
1
+ require "digest/sha2"
2
+
3
+ module Devise
4
+ # Implements a way of adding different encryptions.
5
+ # The class should implement a self.digest method that taks the following params:
6
+ # - password
7
+ # - stretches: the number of times the encryption will be applied
8
+ # - salt: the password salt as defined by devise
9
+ # - pepper: Devise config option
10
+ #
11
+ module Encryptors
12
+ # = Sha512
13
+ # Uses the Sha512 hash algorithm to encrypt passwords.
14
+ class Sha512
15
+
16
+ # Gererates a default password digest based on salt, pepper and the
17
+ # incoming password.
18
+ def self.digest(password, stretches, salt, pepper)
19
+ digest = pepper
20
+ stretches.times { digest = self.secure_digest(salt, digest, password, pepper) }
21
+ digest
22
+ end
23
+
24
+ private
25
+
26
+ # Generate a Sha512 digest joining args. Generated token is something like
27
+ # --arg1--arg2--arg3--argN--
28
+ def self.secure_digest(*tokens)
29
+ ::Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--')
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ module Devise
2
+ module Failure
3
+ mattr_accessor :default_url
4
+
5
+ # Failure application that will be called every time :warden is thrown from
6
+ # any strategy or hook. Responsible for redirect the user to the sign in
7
+ # page based on current scope and mapping. If no scope is given, redirect
8
+ # to the default_url.
9
+ def self.call(env)
10
+ options = env['warden.options']
11
+ scope = options[:scope]
12
+ params = case env['warden'].try(:message)
13
+ when Symbol
14
+ { env['warden'].message => true }
15
+ when String
16
+ { :message => env['warden'].message }
17
+ else
18
+ options[:params]
19
+ end
20
+
21
+ redirect_path = if mapping = Devise.mappings[scope]
22
+ "#{mapping.parsed_path}/#{mapping.path_names[:sign_in]}"
23
+ else
24
+ "/#{default_url}"
25
+ end
26
+
27
+ headers = {}
28
+ headers["Location"] = redirect_path
29
+ headers["Location"] << "?" << Rack::Utils.build_query(params) if params
30
+ headers["Content-Type"] = 'text/plain'
31
+
32
+ message = options[:message] || "You are being redirected to #{redirect_path}"
33
+ [302, headers, [message]]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ # Each time the user is set we verify if it is still able to really sign in.
2
+ # This is done by checking the time frame the user is able to sign in without
3
+ # confirming it's account. If the user has not confirmed it's account during
4
+ # this time frame, he/she will not able to sign in anymore.
5
+ Warden::Manager.after_set_user do |record, auth, options|
6
+ if record && record.respond_to?(:active?) && !record.active?
7
+ scope = options[:scope]
8
+ auth.logout(scope)
9
+ throw :warden, :scope => scope, :params => { :unconfirmed => true }
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # After authenticate hook to verify if the user in the given scope asked to be
2
+ # remembered while he does not sign out. Generates a new remember token for
3
+ # that specific user and adds a cookie with this user info to sign in this user
4
+ # automatically without asking for credentials. Refer to rememberable strategy
5
+ # for more info.
6
+ Warden::Manager.after_authentication do |record, auth, options|
7
+ scope = options[:scope]
8
+ remember_me = auth.params[scope].try(:fetch, :remember_me, nil)
9
+
10
+ if Devise::TRUE_VALUES.include?(remember_me) && record.respond_to?(:remember_me!)
11
+ record.remember_me!
12
+ auth.cookies['remember_token'] = {
13
+ :value => record.class.serialize_into_cookie(record),
14
+ :expires => record.remember_expires_at
15
+ }
16
+ end
17
+ end
18
+
19
+ # Before logout hook to forget the user in the given scope, only if rememberable
20
+ # is activated for this scope. Also clear remember token to ensure the user
21
+ # won't be remembered again.
22
+ Warden::Manager.before_logout do |record, auth, scope|
23
+ if record.respond_to?(:forget_me!)
24
+ record.forget_me!
25
+ auth.cookies.delete('remember_token')
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ en:
2
+ devise:
3
+ sessions:
4
+ signed_in: 'Signed in successfully.'
5
+ signed_out: 'Signed out successfully.'
6
+ unauthenticated: 'You need to sign in or sign up before continuing.'
7
+ unconfirmed: 'You have to confirm your account before continuing.'
8
+ invalid: 'Invalid email or password.'
9
+ passwords:
10
+ send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
11
+ updated: 'Your password was changed successfully. You are now signed in.'
12
+ confirmations:
13
+ send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
14
+ confirmed: 'Your account was successfully confirmed. You are now signed in.'
15
+ mailer:
16
+ confirmation_instructions: 'Confirmation instructions'
17
+ reset_password_instructions: 'Reset password instructions'
18
+
@@ -0,0 +1,120 @@
1
+ module Devise
2
+ # Responsible for handling devise mappings and routes configuration. Each
3
+ # resource configured by devise_for in routes is actually creating a mapping
4
+ # object. You can refer to devise_for in routes for usage options.
5
+ #
6
+ # The required value in devise_for is actually not used internally, but it's
7
+ # inflected to find all other values.
8
+ #
9
+ # map.devise_for :users
10
+ # mapping = Devise.mappings[:user]
11
+ #
12
+ # mapping.name #=> :user
13
+ # # is the scope used in controllers and warden, given in the route as :singular.
14
+ #
15
+ # mapping.as #=> "users"
16
+ # # how the mapping should be search in the path, given in the route as :as.
17
+ #
18
+ # mapping.to #=> User
19
+ # # is the class to be loaded from routes, given in the route as :class_name.
20
+ #
21
+ # mapping.for #=> [:authenticatable]
22
+ # # is the modules included in the class
23
+ #
24
+ class Mapping #:nodoc:
25
+ attr_reader :name, :as, :path_names, :path_prefix
26
+
27
+ # Loop through all mappings looking for a map that matches with the requested
28
+ # path (ie /users/sign_in). If a path prefix is given, it's taken into account.
29
+ def self.find_by_path(path)
30
+ Devise.mappings.each_value do |mapping|
31
+ route = path.split("/")[mapping.as_position]
32
+ return mapping if mapping.as == route.to_sym
33
+ end
34
+ nil
35
+ end
36
+
37
+ # Default url options which can be used as prefix.
38
+ def self.default_url_options
39
+ {}
40
+ end
41
+
42
+ def initialize(name, options) #:nodoc:
43
+ options.assert_valid_keys(:class_name, :as, :path_names, :singular, :path_prefix)
44
+
45
+ @as = (options[:as] || name).to_sym
46
+ @klass = (options[:class_name] || name.to_s.classify).to_s
47
+ @name = (options[:singular] || name.to_s.singularize).to_sym
48
+ @path_names = options[:path_names] || {}
49
+ @path_prefix = options[:path_prefix] || ""
50
+ @path_prefix << "/" unless @path_prefix[-1] == ?/
51
+
52
+ setup_path_names
53
+ end
54
+
55
+ # Return modules for the mapping.
56
+ def for
57
+ @for ||= to.devise_modules
58
+ end
59
+
60
+ # Reload mapped class each time when cache_classes is false.
61
+ def to
62
+ return @to if @to
63
+ klass = @klass.constantize
64
+ @to = klass if Rails.configuration.cache_classes
65
+ klass
66
+ end
67
+
68
+ # Check if the respective controller has a module in the mapping class.
69
+ def allows?(controller)
70
+ self.for.include?(CONTROLLERS[controller.to_sym])
71
+ end
72
+
73
+ # Return in which position in the path prefix devise should find the as mapping.
74
+ def as_position
75
+ self.path_prefix.count("/")
76
+ end
77
+
78
+ # Returns the raw path using path_prefix and as.
79
+ def raw_path
80
+ path_prefix + as.to_s
81
+ end
82
+
83
+ # Returns the parsed path. If you need meta information in your path_prefix,
84
+ # you should overwrite this method to use it. The only information supported
85
+ # by default is I18n.locale.
86
+ #
87
+ def parsed_path
88
+ returning raw_path do |path|
89
+ self.class.default_url_options.each do |key, value|
90
+ path.gsub!(key.inspect, value.to_s)
91
+ end
92
+ end
93
+ end
94
+
95
+ # Create magic predicates for verifying what module is activated by this map.
96
+ # Example:
97
+ #
98
+ # def confirmable?
99
+ # self.for.include?(:confirmable)
100
+ # end
101
+ #
102
+ ALL.each do |m|
103
+ class_eval <<-METHOD, __FILE__, __LINE__
104
+ def #{m}?
105
+ self.for.include?(:#{m})
106
+ end
107
+ METHOD
108
+ end
109
+
110
+ private
111
+
112
+ # Configure default path names, allowing the user overwrite defaults by
113
+ # passing a hash in :path_names.
114
+ def setup_path_names
115
+ [:sign_in, :sign_out, :password, :confirmation].each do |path_name|
116
+ @path_names[path_name] ||= path_name.to_s
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,57 @@
1
+ module Devise
2
+ # Helpers to migration:
3
+ #
4
+ # create_table :accounts do |t|
5
+ # t.authenticatable
6
+ # t.confirmable
7
+ # t.recoverable
8
+ # t.rememberable
9
+ # t.timestamps
10
+ # end
11
+ #
12
+ # However this method does not add indexes. If you need them, here is the declaration:
13
+ #
14
+ # add_index "accounts", ["email"], :name => "email", :unique => true
15
+ # add_index "accounts", ["confirmation_token"], :name => "confirmation_token", :unique => true
16
+ # add_index "accounts", ["reset_password_token"], :name => "reset_password_token", :unique => true
17
+ #
18
+ module Migrations
19
+
20
+ # Creates email, encrypted_password and password_salt.
21
+ #
22
+ # == Options
23
+ # * :null when true, allow columns to be null
24
+ # * :encryptor The encryptor going to be used, necessary for setting the proper encrypter password length
25
+ #
26
+ def authenticatable(options={})
27
+ null = options[:null] || false
28
+ encryptor = options[:encryptor] || :sha1
29
+
30
+ string :email, :null => null, :limit => 100
31
+ string :encrypted_password, :null => null, :limit => Devise::ENCRYPTORS_LENGTH[encryptor]
32
+ string :password_salt, :null => null, :limit => 20
33
+ end
34
+
35
+ # Creates confirmation_token, confirmed_at and confirmation_sent_at.
36
+ #
37
+ def confirmable
38
+ string :confirmation_token, :limit => 20
39
+ datetime :confirmed_at
40
+ datetime :confirmation_sent_at
41
+ end
42
+
43
+ # Creates reset_password_token.
44
+ #
45
+ def recoverable
46
+ string :reset_password_token, :limit => 20
47
+ end
48
+
49
+ # Creates remember_token and remember_created_at.
50
+ #
51
+ def rememberable
52
+ string :remember_token, :limit => 20
53
+ datetime :remember_created_at
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,87 @@
1
+ require 'devise/strategies/authenticatable'
2
+
3
+ module Devise
4
+ module Models
5
+
6
+ # Authenticable Module, responsible for encrypting password and validating
7
+ # authenticity of a user while signing in.
8
+ #
9
+ # Configuration:
10
+ #
11
+ # You can overwrite configuration values by setting in globally in Devise,
12
+ # using devise method or overwriting the respective instance method.
13
+ #
14
+ # pepper: encryption key used for creating encrypted password. Each time
15
+ # password changes, it's gonna be encrypted again, and this key
16
+ # is added to the password and salt to create a secure hash.
17
+ # Always use `rake secret' to generate a new key.
18
+ #
19
+ # stretches: defines how many times the password will be encrypted.
20
+ #
21
+ # Examples:
22
+ #
23
+ # User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
24
+ # User.find(1).valid_password?('password123') # returns true/false
25
+ #
26
+ module Authenticatable
27
+ def self.included(base)
28
+ base.class_eval do
29
+ extend ClassMethods
30
+
31
+ attr_reader :password
32
+ attr_accessor :password_confirmation
33
+ end
34
+ end
35
+
36
+ # Regenerates password salt and encrypted password each time password is
37
+ # setted.
38
+ def password=(new_password)
39
+ @password = new_password
40
+ self.password_salt = friendly_token
41
+ self.encrypted_password = password_digest(@password)
42
+ end
43
+
44
+ # Verifies whether an incoming_password (ie from login) is the user
45
+ # password.
46
+ def valid_password?(incoming_password)
47
+ password_digest(incoming_password) == encrypted_password
48
+ end
49
+
50
+ protected
51
+
52
+ # Digests the password using the configured encryptor
53
+ def password_digest(password)
54
+ encryptor.digest(password, stretches, password_salt, pepper)
55
+ end
56
+
57
+ # Generate a friendly string randomically to be used as token.
58
+ def friendly_token
59
+ ActiveSupport::SecureRandom.base64(15).tr('+/=', '-_ ').strip.delete("\n")
60
+ end
61
+
62
+ module ClassMethods
63
+ # Authenticate a user based on email and password. Returns the
64
+ # authenticated user if it's valid or nil.
65
+ # Attributes are :email and :password
66
+ def authenticate(attributes={})
67
+ authenticatable = find_by_email(attributes[:email])
68
+ authenticatable if authenticatable.try(:valid_password?, attributes[:password])
69
+ end
70
+
71
+ # Attempt to find a user by it's email. If not user is found, returns a
72
+ # new user with an email not found error.
73
+ def find_or_initialize_with_error_by_email(email)
74
+ perishable = find_or_initialize_by_email(email)
75
+ if perishable.new_record?
76
+ perishable.errors.add(:email, :not_found, :default => 'not found')
77
+ end
78
+ perishable
79
+ end
80
+ end
81
+
82
+ Devise::Models.config(self, :pepper)
83
+ Devise::Models.config(self, :stretches)
84
+ Devise::Models.config(self, :encryptor)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,156 @@
1
+ require 'devise/hooks/confirmable'
2
+
3
+ module Devise
4
+ module Models
5
+
6
+ # Confirmable is responsible to verify if an account is already confirmed to
7
+ # sign in, and to send emails with confirmation instructions.
8
+ # Confirmation instructions are sent to the user email after creating a
9
+ # record, after updating it's email and also when manually requested by
10
+ # a new confirmation instruction request.
11
+ # Whenever the user update it's email, his account is automatically unconfirmed,
12
+ # it means it won't be able to sign in again without confirming the account
13
+ # again through the email that was sent.
14
+ #
15
+ # Configuration:
16
+ #
17
+ # confirm_within: the time you want the user will have to confirm it's account
18
+ # without blocking his access. When confirm_within is zero, the
19
+ # user won't be able to sign in without confirming. You can
20
+ # use this to let your user access some features of your
21
+ # application without confirming the account, but blocking it
22
+ # after a certain period (ie 7 days). By default confirm_within is
23
+ # zero, it means users always have to confirm to sign in.
24
+ #
25
+ # Examples:
26
+ #
27
+ # User.find(1).confirm! # returns true unless it's already confirmed
28
+ # User.find(1).confirmed? # true/false
29
+ # User.find(1).send_confirmation_instructions # manually send instructions
30
+ # User.find(1).reset_confirmation! # reset confirmation status and send instructions
31
+ module Confirmable
32
+
33
+ def self.included(base)
34
+ base.class_eval do
35
+ extend ClassMethods
36
+
37
+ before_create :generate_confirmation_token
38
+ after_create :send_confirmation_instructions
39
+ end
40
+ end
41
+
42
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
43
+ # is already confirmed, add en error to email field
44
+ def confirm!
45
+ unless_confirmed do
46
+ self.confirmation_token = nil
47
+ self.confirmed_at = Time.now
48
+ save(false)
49
+ end
50
+ end
51
+
52
+ # Verifies whether a user is confirmed or not
53
+ def confirmed?
54
+ !new_record? && confirmed_at?
55
+ end
56
+
57
+ # Send confirmation instructions by email
58
+ def send_confirmation_instructions
59
+ ::DeviseMailer.deliver_confirmation_instructions(self)
60
+ end
61
+
62
+ # Remove confirmation date and send confirmation instructions, to ensure
63
+ # after sending these instructions the user won't be able to sign in without
64
+ # confirming it's account
65
+ def reset_confirmation!
66
+ unless_confirmed do
67
+ generate_confirmation_token
68
+ save(false)
69
+ send_confirmation_instructions
70
+ end
71
+ end
72
+
73
+ # Verify whether a user is active to sign in or not. If the user is
74
+ # already confirmed, it should never be blocked. Otherwise we need to
75
+ # calculate if the confirm time has not expired for this user, in other
76
+ # words, if the confirmation is still valid.
77
+ def active?
78
+ confirmed? || confirmation_period_valid?
79
+ end
80
+
81
+ protected
82
+
83
+ # Checks if the confirmation for the user is within the limit time.
84
+ # We do this by calculating if the difference between today and the
85
+ # confirmation sent date does not exceed the confirm in time configured.
86
+ # Confirm_in is a model configuration, must always be an integer value.
87
+ #
88
+ # Example:
89
+ #
90
+ # # confirm_within = 1.day and confirmation_sent_at = today
91
+ # confirmation_period_valid? # returns true
92
+ #
93
+ # # confirm_within = 5.days and confirmation_sent_at = 4.days.ago
94
+ # confirmation_period_valid? # returns true
95
+ #
96
+ # # confirm_within = 5.days and confirmation_sent_at = 5.days.ago
97
+ # confirmation_period_valid? # returns false
98
+ #
99
+ # # confirm_within = 0.days
100
+ # confirmation_period_valid? # will always return false
101
+ #
102
+ def confirmation_period_valid?
103
+ confirmation_sent_at? &&
104
+ (Time.now.utc - confirmation_sent_at.utc) < confirm_within
105
+ end
106
+
107
+ # Checks whether the record is confirmed or not, yielding to the block
108
+ # if it's already confirmed, otherwise adds an error to email.
109
+ def unless_confirmed
110
+ unless confirmed?
111
+ yield
112
+ else
113
+ errors.add(:email, :already_confirmed, :default => 'already confirmed')
114
+ false
115
+ end
116
+ end
117
+
118
+ # Generates a new random token for confirmation, and stores the time
119
+ # this token is being generated
120
+ def generate_confirmation_token
121
+ self.confirmed_at = nil
122
+ self.confirmation_token = friendly_token
123
+ self.confirmation_sent_at = Time.now.utc
124
+ end
125
+
126
+ module ClassMethods
127
+
128
+ # Attempt to find a user by it's email. If a record is found, send new
129
+ # confirmation instructions to it. If not user is found, returns a new user
130
+ # with an email not found error.
131
+ # Options must contain the user email
132
+ def send_confirmation_instructions(attributes={})
133
+ confirmable = find_or_initialize_with_error_by_email(attributes[:email])
134
+ confirmable.reset_confirmation! unless confirmable.new_record?
135
+ confirmable
136
+ end
137
+
138
+ # Find a user by it's confirmation token and try to confirm it.
139
+ # If no user is found, returns a new user with an error.
140
+ # If the user is already confirmed, create an error for the user
141
+ # Options must have the confirmation_token
142
+ def confirm!(attributes={})
143
+ confirmable = find_or_initialize_by_confirmation_token(attributes[:confirmation_token])
144
+ if confirmable.new_record?
145
+ confirmable.errors.add(:confirmation_token, :invalid)
146
+ else
147
+ confirmable.confirm!
148
+ end
149
+ confirmable
150
+ end
151
+ end
152
+
153
+ Devise::Models.config(self, :confirm_within)
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,88 @@
1
+ module Devise
2
+ module Models
3
+
4
+ # Recoverable takes care of reseting the user password and send reset instructions
5
+ # Examples:
6
+ #
7
+ # # resets the user password and save the record, true if valid passwords are given, otherwise false
8
+ # User.find(1).reset_password!('password123', 'password123')
9
+ # # only resets the user password, without saving the record
10
+ # user = User.find(1)
11
+ # user.reset_password('password123', 'password123')
12
+ # # creates a new token and send it with instructions about how to reset the password
13
+ # User.find(1).send_reset_password_instructions
14
+ module Recoverable
15
+ def self.included(base)
16
+ base.class_eval do
17
+ extend ClassMethods
18
+ end
19
+ end
20
+
21
+ # Update password
22
+ def reset_password(new_password, new_password_confirmation)
23
+ self.password = new_password
24
+ self.password_confirmation = new_password_confirmation
25
+ end
26
+
27
+ # Update password saving the record and clearing token. Returns true if
28
+ # the passwords are valid and the record was saved, false otherwise.
29
+ def reset_password!(new_password, new_password_confirmation)
30
+ reset_password(new_password, new_password_confirmation)
31
+ clear_reset_password_token if valid?
32
+ save
33
+ end
34
+
35
+ # Resets reset password token and send reset password instructions by email
36
+ def send_reset_password_instructions
37
+ generate_reset_password_token!
38
+ ::DeviseMailer.deliver_reset_password_instructions(self)
39
+ end
40
+
41
+ protected
42
+
43
+ # Generates a new random token for reset password
44
+ def generate_reset_password_token
45
+ self.reset_password_token = friendly_token
46
+ end
47
+
48
+ # Resets the reset password token with and save the record without
49
+ # validating
50
+ def generate_reset_password_token!
51
+ generate_reset_password_token && save(false)
52
+ end
53
+
54
+ # Removes reset_password token
55
+ def clear_reset_password_token
56
+ self.reset_password_token = nil
57
+ end
58
+
59
+ module ClassMethods
60
+
61
+ # Attempt to find a user by it's email. If a record is found, send new
62
+ # password instructions to it. If not user is found, returns a new user
63
+ # with an email not found error.
64
+ # Attributes must contain the user email
65
+ def send_reset_password_instructions(attributes={})
66
+ recoverable = find_or_initialize_with_error_by_email(attributes[:email])
67
+ recoverable.send_reset_password_instructions unless recoverable.new_record?
68
+ recoverable
69
+ end
70
+
71
+ # Attempt to find a user by it's reset_password_token to reset it's
72
+ # password. If a user is found, reset it's password and automatically
73
+ # try saving the record. If not user is found, returns a new user
74
+ # containing an error in reset_password_token attribute.
75
+ # Attributes must contain reset_password_token, password and confirmation
76
+ def reset_password!(attributes={})
77
+ recoverable = find_or_initialize_by_reset_password_token(attributes[:reset_password_token])
78
+ if recoverable.new_record?
79
+ recoverable.errors.add(:reset_password_token, :invalid)
80
+ else
81
+ recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
82
+ end
83
+ recoverable
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end