shingara-devise 0.4.3

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 (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