devise-bootstrap 0.0.1

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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +31 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/app/controllers/devise/confirmations_controller.rb +47 -0
  8. data/app/controllers/devise/omniauth_callbacks_controller.rb +30 -0
  9. data/app/controllers/devise/passwords_controller.rb +70 -0
  10. data/app/controllers/devise/registrations_controller.rb +137 -0
  11. data/app/controllers/devise/sessions_controller.rb +53 -0
  12. data/app/controllers/devise/unlocks_controller.rb +46 -0
  13. data/app/controllers/devise_controller.rb +176 -0
  14. data/app/helpers/devise_helper.rb +25 -0
  15. data/app/mailers/devise/mailer.rb +20 -0
  16. data/app/views/devise/confirmations/new.html.erb +12 -0
  17. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  18. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  19. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  20. data/app/views/devise/passwords/edit.html.erb +16 -0
  21. data/app/views/devise/passwords/new.html.erb +12 -0
  22. data/app/views/devise/registrations/edit.html.erb +29 -0
  23. data/app/views/devise/registrations/new.html.erb +18 -0
  24. data/app/views/devise/sessions/new.html.erb +17 -0
  25. data/app/views/devise/shared/_links.erb +25 -0
  26. data/app/views/devise/unlocks/new.html.erb +12 -0
  27. data/config/locales/en.yml +59 -0
  28. data/devise-bootstrap.gemspec +30 -0
  29. data/gemfiles/Gemfile.rails-3.2-stable +29 -0
  30. data/gemfiles/Gemfile.rails-4.0-stable +29 -0
  31. data/gemfiles/Gemfile.rails-head +29 -0
  32. data/lib/devise/bootstrap.rb +7 -0
  33. data/lib/devise/bootstrap/version.rb +5 -0
  34. data/lib/devise/devise.rb +491 -0
  35. data/lib/devise/devise/controllers/helpers.rb +213 -0
  36. data/lib/devise/devise/controllers/rememberable.rb +47 -0
  37. data/lib/devise/devise/controllers/scoped_views.rb +17 -0
  38. data/lib/devise/devise/controllers/sign_in_out.rb +103 -0
  39. data/lib/devise/devise/controllers/store_location.rb +50 -0
  40. data/lib/devise/devise/controllers/url_helpers.rb +67 -0
  41. data/lib/devise/devise/delegator.rb +16 -0
  42. data/lib/devise/devise/failure_app.rb +205 -0
  43. data/lib/devise/devise/hooks/activatable.rb +11 -0
  44. data/lib/devise/devise/hooks/csrf_cleaner.rb +5 -0
  45. data/lib/devise/devise/hooks/forgetable.rb +9 -0
  46. data/lib/devise/devise/hooks/lockable.rb +7 -0
  47. data/lib/devise/devise/hooks/proxy.rb +21 -0
  48. data/lib/devise/devise/hooks/rememberable.rb +7 -0
  49. data/lib/devise/devise/hooks/timeoutable.rb +28 -0
  50. data/lib/devise/devise/hooks/trackable.rb +9 -0
  51. data/lib/devise/devise/mailers/helpers.rb +90 -0
  52. data/lib/devise/devise/mapping.rb +172 -0
  53. data/lib/devise/devise/models.rb +119 -0
  54. data/lib/devise/devise/models/authenticatable.rb +284 -0
  55. data/lib/devise/devise/models/confirmable.rb +295 -0
  56. data/lib/devise/devise/models/database_authenticatable.rb +164 -0
  57. data/lib/devise/devise/models/lockable.rb +196 -0
  58. data/lib/devise/devise/models/omniauthable.rb +27 -0
  59. data/lib/devise/devise/models/recoverable.rb +131 -0
  60. data/lib/devise/devise/models/registerable.rb +25 -0
  61. data/lib/devise/devise/models/rememberable.rb +129 -0
  62. data/lib/devise/devise/models/timeoutable.rb +49 -0
  63. data/lib/devise/devise/models/trackable.rb +35 -0
  64. data/lib/devise/devise/models/validatable.rb +66 -0
  65. data/lib/devise/devise/modules.rb +28 -0
  66. data/lib/devise/devise/omniauth.rb +28 -0
  67. data/lib/devise/devise/omniauth/config.rb +45 -0
  68. data/lib/devise/devise/omniauth/url_helpers.rb +18 -0
  69. data/lib/devise/devise/orm/active_record.rb +3 -0
  70. data/lib/devise/devise/orm/mongoid.rb +3 -0
  71. data/lib/devise/devise/parameter_filter.rb +40 -0
  72. data/lib/devise/devise/parameter_sanitizer.rb +99 -0
  73. data/lib/devise/devise/rails.rb +56 -0
  74. data/lib/devise/devise/rails/routes.rb +496 -0
  75. data/lib/devise/devise/rails/warden_compat.rb +22 -0
  76. data/lib/devise/devise/strategies/authenticatable.rb +167 -0
  77. data/lib/devise/devise/strategies/base.rb +20 -0
  78. data/lib/devise/devise/strategies/database_authenticatable.rb +23 -0
  79. data/lib/devise/devise/strategies/rememberable.rb +55 -0
  80. data/lib/devise/devise/test_helpers.rb +132 -0
  81. data/lib/devise/devise/time_inflector.rb +14 -0
  82. data/lib/devise/devise/token_generator.rb +70 -0
  83. data/lib/devise/devise/version.rb +3 -0
  84. data/lib/devise/generators/active_record/devise_generator.rb +73 -0
  85. data/lib/devise/generators/active_record/templates/migration.rb +18 -0
  86. data/lib/devise/generators/active_record/templates/migration_existing.rb +25 -0
  87. data/lib/devise/generators/devise/devise_generator.rb +26 -0
  88. data/lib/devise/generators/devise/install_generator.rb +29 -0
  89. data/lib/devise/generators/devise/orm_helpers.rb +51 -0
  90. data/lib/devise/generators/devise/views_generator.rb +135 -0
  91. data/lib/devise/generators/mongoid/devise_generator.rb +55 -0
  92. data/lib/devise/generators/templates/README +35 -0
  93. data/lib/devise/generators/templates/devise.rb +260 -0
  94. data/lib/devise/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  95. data/lib/devise/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  96. data/lib/devise/generators/templates/markerb/unlock_instructions.markerb +7 -0
  97. data/lib/devise/generators/templates/simple_form_for/confirmations/new.html.erb +16 -0
  98. data/lib/devise/generators/templates/simple_form_for/passwords/edit.html.erb +19 -0
  99. data/lib/devise/generators/templates/simple_form_for/passwords/new.html.erb +15 -0
  100. data/lib/devise/generators/templates/simple_form_for/registrations/edit.html.erb +27 -0
  101. data/lib/devise/generators/templates/simple_form_for/registrations/new.html.erb +17 -0
  102. data/lib/devise/generators/templates/simple_form_for/sessions/new.html.erb +15 -0
  103. data/lib/devise/generators/templates/simple_form_for/unlocks/new.html.erb +16 -0
  104. metadata +250 -0
@@ -0,0 +1,119 @@
1
+ module Devise
2
+ module Models
3
+ class MissingAttribute < StandardError
4
+ def initialize(attributes)
5
+ @attributes = attributes
6
+ end
7
+
8
+ def message
9
+ "The following attribute(s) is (are) missing on your model: #{@attributes.join(", ")}"
10
+ end
11
+ end
12
+
13
+ # Creates configuration values for Devise and for the given module.
14
+ #
15
+ # Devise::Models.config(Devise::Authenticatable, :stretches, 10)
16
+ #
17
+ # The line above creates:
18
+ #
19
+ # 1) An accessor called Devise.stretches, which value is used by default;
20
+ #
21
+ # 2) Some class methods for your model Model.stretches and Model.stretches=
22
+ # which have higher priority than Devise.stretches;
23
+ #
24
+ # 3) And an instance method stretches.
25
+ #
26
+ # To add the class methods you need to have a module ClassMethods defined
27
+ # inside the given class.
28
+ #
29
+ def self.config(mod, *accessors) #:nodoc:
30
+ class << mod; attr_accessor :available_configs; end
31
+ mod.available_configs = accessors
32
+
33
+ accessors.each do |accessor|
34
+ mod.class_eval <<-METHOD, __FILE__, __LINE__ + 1
35
+ def #{accessor}
36
+ if defined?(@#{accessor})
37
+ @#{accessor}
38
+ elsif superclass.respond_to?(:#{accessor})
39
+ superclass.#{accessor}
40
+ else
41
+ Devise.#{accessor}
42
+ end
43
+ end
44
+
45
+ def #{accessor}=(value)
46
+ @#{accessor} = value
47
+ end
48
+ METHOD
49
+ end
50
+ end
51
+
52
+ def self.check_fields!(klass)
53
+ failed_attributes = []
54
+ instance = klass.new
55
+
56
+ klass.devise_modules.each do |mod|
57
+ constant = const_get(mod.to_s.classify)
58
+
59
+ constant.required_fields(klass).each do |field|
60
+ failed_attributes << field unless instance.respond_to?(field)
61
+ end
62
+ end
63
+
64
+ if failed_attributes.any?
65
+ fail Devise::Models::MissingAttribute.new(failed_attributes)
66
+ end
67
+ end
68
+
69
+ # Include the chosen devise modules in your model:
70
+ #
71
+ # devise :database_authenticatable, :confirmable, :recoverable
72
+ #
73
+ # You can also give any of the devise configuration values in form of a hash,
74
+ # with specific values for this model. Please check your Devise initializer
75
+ # for a complete description on those values.
76
+ #
77
+ def devise(*modules)
78
+ options = modules.extract_options!.dup
79
+
80
+ selected_modules = modules.map(&:to_sym).uniq.sort_by do |s|
81
+ Devise::ALL.index(s) || -1 # follow Devise::ALL order
82
+ end
83
+
84
+ devise_modules_hook! do
85
+ include Devise::Models::Authenticatable
86
+
87
+ selected_modules.each do |m|
88
+ mod = Devise::Models.const_get(m.to_s.classify)
89
+
90
+ if mod.const_defined?("ClassMethods")
91
+ class_mod = mod.const_get("ClassMethods")
92
+ extend class_mod
93
+
94
+ if class_mod.respond_to?(:available_configs)
95
+ available_configs = class_mod.available_configs
96
+ available_configs.each do |config|
97
+ next unless options.key?(config)
98
+ send(:"#{config}=", options.delete(config))
99
+ end
100
+ end
101
+ end
102
+
103
+ include mod
104
+ end
105
+
106
+ self.devise_modules |= selected_modules
107
+ options.each { |key, value| send(:"#{key}=", value) }
108
+ end
109
+ end
110
+
111
+ # The hook which is called inside devise.
112
+ # So your ORM can include devise compatibility stuff.
113
+ def devise_modules_hook!
114
+ yield
115
+ end
116
+ end
117
+ end
118
+
119
+ require 'devise/models/authenticatable'
@@ -0,0 +1,284 @@
1
+ require 'devise/hooks/activatable'
2
+ require 'devise/hooks/csrf_cleaner'
3
+
4
+ module Devise
5
+ module Models
6
+ # Authenticatable module. Holds common settings for authentication.
7
+ #
8
+ # == Options
9
+ #
10
+ # Authenticatable adds the following options to devise_for:
11
+ #
12
+ # * +authentication_keys+: parameters used for authentication. By default [:email].
13
+ #
14
+ # * +http_authentication_key+: map the username passed via HTTP Auth to this parameter. Defaults to
15
+ # the first element in +authentication_keys+.
16
+ #
17
+ # * +request_keys+: parameters from the request object used for authentication.
18
+ # By specifying a symbol (which should be a request method), it will automatically be
19
+ # passed to find_for_authentication method and considered in your model lookup.
20
+ #
21
+ # For instance, if you set :request_keys to [:subdomain], :subdomain will be considered
22
+ # as key on authentication. This can also be a hash where the value is a boolean specifying
23
+ # if the value is required or not.
24
+ #
25
+ # * +http_authenticatable+: if this model allows http authentication. By default false.
26
+ # It also accepts an array specifying the strategies that should allow http.
27
+ #
28
+ # * +params_authenticatable+: if this model allows authentication through request params. By default true.
29
+ # It also accepts an array specifying the strategies that should allow params authentication.
30
+ #
31
+ # * +skip_session_storage+: By default Devise will store the user in session.
32
+ # By default is set to skip_session_storage: [:http_auth].
33
+ #
34
+ # == active_for_authentication?
35
+ #
36
+ # After authenticating a user and in each request, Devise checks if your model is active by
37
+ # calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
38
+ # :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
39
+ #
40
+ # You overwrite this method yourself, but if you do, don't forget to call super:
41
+ #
42
+ # def active_for_authentication?
43
+ # super && special_condition_is_valid?
44
+ # end
45
+ #
46
+ # Whenever active_for_authentication? returns false, Devise asks the reason why your model is inactive using
47
+ # the inactive_message method. You can overwrite it as well:
48
+ #
49
+ # def inactive_message
50
+ # special_condition_is_valid? ? super : :special_condition_is_not_valid
51
+ # end
52
+ #
53
+ module Authenticatable
54
+ extend ActiveSupport::Concern
55
+
56
+ BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
57
+ :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
58
+ :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
59
+ :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
60
+
61
+ included do
62
+ class_attribute :devise_modules, instance_writer: false
63
+ self.devise_modules ||= []
64
+
65
+ before_validation :downcase_keys
66
+ before_validation :strip_whitespace
67
+ end
68
+
69
+ def self.required_fields(klass)
70
+ []
71
+ end
72
+
73
+ # Check if the current object is valid for authentication. This method and
74
+ # find_for_authentication are the methods used in a Warden::Strategy to check
75
+ # if a model should be signed in or not.
76
+ #
77
+ # However, you should not overwrite this method, you should overwrite active_for_authentication?
78
+ # and inactive_message instead.
79
+ def valid_for_authentication?
80
+ block_given? ? yield : true
81
+ end
82
+
83
+ def unauthenticated_message
84
+ :invalid
85
+ end
86
+
87
+ def active_for_authentication?
88
+ true
89
+ end
90
+
91
+ def inactive_message
92
+ :inactive
93
+ end
94
+
95
+ def authenticatable_salt
96
+ end
97
+
98
+ array = %w(serializable_hash)
99
+ # to_xml does not call serializable_hash on 3.1
100
+ array << "to_xml" if Rails::VERSION::STRING[0,3] == "3.1"
101
+
102
+ array.each do |method|
103
+ class_eval <<-RUBY, __FILE__, __LINE__
104
+ # Redefine to_xml and serializable_hash in models for more secure defaults.
105
+ # By default, it removes from the serializable model all attributes that
106
+ # are *not* accessible. You can remove this default by using :force_except
107
+ # and passing a new list of attributes you want to exempt. All attributes
108
+ # given to :except will simply add names to exempt to Devise internal list.
109
+ def #{method}(options=nil)
110
+ options ||= {}
111
+ options[:except] = Array(options[:except])
112
+
113
+ if options[:force_except]
114
+ options[:except].concat Array(options[:force_except])
115
+ else
116
+ options[:except].concat BLACKLIST_FOR_SERIALIZATION
117
+ end
118
+ super(options)
119
+ end
120
+ RUBY
121
+ end
122
+
123
+ protected
124
+
125
+ def devise_mailer
126
+ Devise.mailer
127
+ end
128
+
129
+ # This is an internal method called every time Devise needs
130
+ # to send a notification/mail. This can be overridden if you
131
+ # need to customize the e-mail delivery logic. For instance,
132
+ # if you are using a queue to deliver e-mails (delayed job,
133
+ # sidekiq, resque, etc), you must add the delivery to the queue
134
+ # just after the transaction was committed. To achieve this,
135
+ # you can override send_devise_notification to store the
136
+ # deliveries until the after_commit callback is triggered:
137
+ #
138
+ # class User
139
+ # devise :database_authenticatable, :confirmable
140
+ #
141
+ # after_commit :send_pending_notifications
142
+ #
143
+ # protected
144
+ #
145
+ # def send_devise_notification(notification, *args)
146
+ # # If the record is new or changed then delay the
147
+ # # delivery until the after_commit callback otherwise
148
+ # # send now because after_commit will not be called.
149
+ # if new_record? || changed?
150
+ # pending_notifications << [notification, args]
151
+ # else
152
+ # devise_mailer.send(notification, self, *args).deliver
153
+ # end
154
+ # end
155
+ #
156
+ # def send_pending_notifications
157
+ # pending_notifications.each do |notification, args|
158
+ # devise_mailer.send(notification, self, *args).deliver
159
+ # end
160
+ #
161
+ # # Empty the pending notifications array because the
162
+ # # after_commit hook can be called multiple times which
163
+ # # could cause multiple emails to be sent.
164
+ # pending_notifications.clear
165
+ # end
166
+ #
167
+ # def pending_notifications
168
+ # @pending_notifications ||= []
169
+ # end
170
+ # end
171
+ #
172
+ def send_devise_notification(notification, *args)
173
+ devise_mailer.send(notification, self, *args).deliver
174
+ end
175
+
176
+ def downcase_keys
177
+ self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase) }
178
+ end
179
+
180
+ def strip_whitespace
181
+ self.class.strip_whitespace_keys.each { |k| apply_to_attribute_or_variable(k, :strip) }
182
+ end
183
+
184
+ def apply_to_attribute_or_variable(attr, method)
185
+ if self[attr]
186
+ self[attr] = self[attr].try(method)
187
+
188
+ # Use respond_to? here to avoid a regression where globally
189
+ # configured strip_whitespace_keys or case_insensitive_keys were
190
+ # attempting to strip or downcase when a model didn't have the
191
+ # globally configured key.
192
+ elsif respond_to?(attr) && respond_to?("#{attr}=")
193
+ new_value = send(attr).try(method)
194
+ send("#{attr}=", new_value)
195
+ end
196
+ end
197
+
198
+ module ClassMethods
199
+ Devise::Models.config(self, :authentication_keys, :request_keys, :strip_whitespace_keys,
200
+ :case_insensitive_keys, :http_authenticatable, :params_authenticatable, :skip_session_storage,
201
+ :http_authentication_key)
202
+
203
+ def serialize_into_session(record)
204
+ [record.to_key, record.authenticatable_salt]
205
+ end
206
+
207
+ def serialize_from_session(key, salt)
208
+ record = to_adapter.get(key)
209
+ record if record && record.authenticatable_salt == salt
210
+ end
211
+
212
+ def params_authenticatable?(strategy)
213
+ params_authenticatable.is_a?(Array) ?
214
+ params_authenticatable.include?(strategy) : params_authenticatable
215
+ end
216
+
217
+ def http_authenticatable?(strategy)
218
+ http_authenticatable.is_a?(Array) ?
219
+ http_authenticatable.include?(strategy) : http_authenticatable
220
+ end
221
+
222
+ # Find first record based on conditions given (ie by the sign in form).
223
+ # This method is always called during an authentication process but
224
+ # it may be wrapped as well. For instance, database authenticatable
225
+ # provides a `find_for_database_authentication` that wraps a call to
226
+ # this method. This allows you to customize both database authenticatable
227
+ # or the whole authenticate stack by customize `find_for_authentication.`
228
+ #
229
+ # Overwrite to add customized conditions, create a join, or maybe use a
230
+ # namedscope to filter records while authenticating.
231
+ # Example:
232
+ #
233
+ # def self.find_for_authentication(tainted_conditions)
234
+ # find_first_by_auth_conditions(tainted_conditions, active: true)
235
+ # end
236
+ #
237
+ # Finally, notice that Devise also queries for users in other scenarios
238
+ # besides authentication, for example when retrieving an user to send
239
+ # an e-mail for password reset. In such cases, find_for_authentication
240
+ # is not called.
241
+ def find_for_authentication(tainted_conditions)
242
+ find_first_by_auth_conditions(tainted_conditions)
243
+ end
244
+
245
+ def find_first_by_auth_conditions(tainted_conditions, opts={})
246
+ to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
247
+ end
248
+
249
+ # Find an initialize a record setting an error if it can't be found.
250
+ def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
251
+ find_or_initialize_with_errors([attribute], { attribute => value }, error)
252
+ end
253
+
254
+ # Find an initialize a group of attributes based on a list of required attributes.
255
+ def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
256
+ attributes = attributes.slice(*required_attributes)
257
+ attributes.delete_if { |key, value| value.blank? }
258
+
259
+ if attributes.size == required_attributes.size
260
+ record = find_first_by_auth_conditions(attributes)
261
+ end
262
+
263
+ unless record
264
+ record = new
265
+
266
+ required_attributes.each do |key|
267
+ value = attributes[key]
268
+ record.send("#{key}=", value)
269
+ record.errors.add(key, value.present? ? error : :blank)
270
+ end
271
+ end
272
+
273
+ record
274
+ end
275
+
276
+ protected
277
+
278
+ def devise_parameter_filter
279
+ @devise_parameter_filter ||= Devise::ParameterFilter.new(case_insensitive_keys, strip_whitespace_keys)
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,295 @@
1
+ module Devise
2
+ module Models
3
+ # Confirmable is responsible to verify if an account is already confirmed to
4
+ # sign in, and to send emails with confirmation instructions.
5
+ # Confirmation instructions are sent to the user email after creating a
6
+ # record and when manually requested by a new confirmation instruction request.
7
+ #
8
+ # == Options
9
+ #
10
+ # Confirmable adds the following options to +devise+:
11
+ #
12
+ # * +allow_unconfirmed_access_for+: the time you want to allow the user to access their account
13
+ # before confirming it. After this period, the user access is denied. You can
14
+ # use this to let your user access some features of your application without
15
+ # confirming the account, but blocking it after a certain period (ie 7 days).
16
+ # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
17
+ # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
18
+ # initial account confirmation) to be applied. Requires additional unconfirmed_email
19
+ # db field to be setup (t.reconfirmable in migrations). Until confirmed new email is
20
+ # stored in unconfirmed email column, and copied to email column on successful
21
+ # confirmation.
22
+ # * +confirm_within+: the time before a sent confirmation token becomes invalid.
23
+ # You can use this to force the user to confirm within a set period of time.
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
+ #
31
+ module Confirmable
32
+ extend ActiveSupport::Concern
33
+ include ActionView::Helpers::DateHelper
34
+
35
+ included do
36
+ before_create :generate_confirmation_token, if: :confirmation_required?
37
+ after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
38
+ before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
39
+ after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
40
+ end
41
+
42
+ def initialize(*args, &block)
43
+ @bypass_confirmation_postpone = false
44
+ @reconfirmation_required = false
45
+ @skip_confirmation_notification = false
46
+ @raw_confirmation_token = nil
47
+ super
48
+ end
49
+
50
+ def self.required_fields(klass)
51
+ required_methods = [:confirmation_token, :confirmed_at, :confirmation_sent_at]
52
+ required_methods << :unconfirmed_email if klass.reconfirmable
53
+ required_methods
54
+ end
55
+
56
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
57
+ # is already confirmed, add an error to email field. If the user is invalid
58
+ # add errors
59
+ def confirm!
60
+ pending_any_confirmation do
61
+ if confirmation_period_expired?
62
+ self.errors.add(:email, :confirmation_period_expired,
63
+ period: Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
64
+ return false
65
+ end
66
+
67
+ self.confirmation_token = nil
68
+ self.confirmed_at = Time.now.utc
69
+
70
+ saved = if self.class.reconfirmable && unconfirmed_email.present?
71
+ skip_reconfirmation!
72
+ self.email = unconfirmed_email
73
+ self.unconfirmed_email = nil
74
+
75
+ # We need to validate in such cases to enforce e-mail uniqueness
76
+ save(validate: true)
77
+ else
78
+ save(validate: false)
79
+ end
80
+
81
+ after_confirmation if saved
82
+ saved
83
+ end
84
+ end
85
+
86
+ # Verifies whether a user is confirmed or not
87
+ def confirmed?
88
+ !!confirmed_at
89
+ end
90
+
91
+ def pending_reconfirmation?
92
+ self.class.reconfirmable && unconfirmed_email.present?
93
+ end
94
+
95
+ # Send confirmation instructions by email
96
+ def send_confirmation_instructions
97
+ unless @raw_confirmation_token
98
+ generate_confirmation_token!
99
+ end
100
+
101
+ opts = pending_reconfirmation? ? { to: unconfirmed_email } : { }
102
+ send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
103
+ end
104
+
105
+ def send_reconfirmation_instructions
106
+ @reconfirmation_required = false
107
+
108
+ unless @skip_confirmation_notification
109
+ send_confirmation_instructions
110
+ end
111
+ end
112
+
113
+ # Resend confirmation token.
114
+ # Regenerates the token if the period is expired.
115
+ def resend_confirmation_instructions
116
+ pending_any_confirmation do
117
+ send_confirmation_instructions
118
+ end
119
+ end
120
+
121
+ # Overwrites active_for_authentication? for confirmation
122
+ # by verifying whether a user is active to sign in or not. If the user
123
+ # is already confirmed, it should never be blocked. Otherwise we need to
124
+ # calculate if the confirm time has not expired for this user.
125
+ def active_for_authentication?
126
+ super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
127
+ end
128
+
129
+ # The message to be shown if the account is inactive.
130
+ def inactive_message
131
+ !confirmed? ? :unconfirmed : super
132
+ end
133
+
134
+ # If you don't want confirmation to be sent on create, neither a code
135
+ # to be generated, call skip_confirmation!
136
+ def skip_confirmation!
137
+ self.confirmed_at = Time.now.utc
138
+ end
139
+
140
+ # Skips sending the confirmation/reconfirmation notification email after_create/after_update. Unlike
141
+ # #skip_confirmation!, record still requires confirmation.
142
+ def skip_confirmation_notification!
143
+ @skip_confirmation_notification = true
144
+ end
145
+
146
+ # If you don't want reconfirmation to be sent, neither a code
147
+ # to be generated, call skip_reconfirmation!
148
+ def skip_reconfirmation!
149
+ @bypass_confirmation_postpone = true
150
+ end
151
+
152
+ protected
153
+
154
+ # A callback method used to deliver confirmation
155
+ # instructions on creation. This can be overridden
156
+ # in models to map to a nice sign up e-mail.
157
+ def send_on_create_confirmation_instructions
158
+ send_confirmation_instructions
159
+ end
160
+
161
+ # Callback to overwrite if confirmation is required or not.
162
+ def confirmation_required?
163
+ !confirmed?
164
+ end
165
+
166
+ # Checks if the confirmation for the user is within the limit time.
167
+ # We do this by calculating if the difference between today and the
168
+ # confirmation sent date does not exceed the confirm in time configured.
169
+ # Confirm_within is a model configuration, must always be an integer value.
170
+ #
171
+ # Example:
172
+ #
173
+ # # allow_unconfirmed_access_for = 1.day and confirmation_sent_at = today
174
+ # confirmation_period_valid? # returns true
175
+ #
176
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 4.days.ago
177
+ # confirmation_period_valid? # returns true
178
+ #
179
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 5.days.ago
180
+ # confirmation_period_valid? # returns false
181
+ #
182
+ # # allow_unconfirmed_access_for = 0.days
183
+ # confirmation_period_valid? # will always return false
184
+ #
185
+ # # allow_unconfirmed_access_for = nil
186
+ # confirmation_period_valid? # will always return true
187
+ #
188
+ def confirmation_period_valid?
189
+ self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
190
+ end
191
+
192
+ # Checks if the user confirmation happens before the token becomes invalid
193
+ # Examples:
194
+ #
195
+ # # confirm_within = 3.days and confirmation_sent_at = 2.days.ago
196
+ # confirmation_period_expired? # returns false
197
+ #
198
+ # # confirm_within = 3.days and confirmation_sent_at = 4.days.ago
199
+ # confirmation_period_expired? # returns true
200
+ #
201
+ # # confirm_within = nil
202
+ # confirmation_period_expired? # will always return false
203
+ #
204
+ def confirmation_period_expired?
205
+ self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
206
+ end
207
+
208
+ # Checks whether the record requires any confirmation.
209
+ def pending_any_confirmation
210
+ if (!confirmed? || pending_reconfirmation?)
211
+ yield
212
+ else
213
+ self.errors.add(:email, :already_confirmed)
214
+ false
215
+ end
216
+ end
217
+
218
+ # Generates a new random token for confirmation, and stores
219
+ # the time this token is being generated
220
+ def generate_confirmation_token
221
+ raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
222
+ @raw_confirmation_token = raw
223
+ self.confirmation_token = enc
224
+ self.confirmation_sent_at = Time.now.utc
225
+ end
226
+
227
+ def generate_confirmation_token!
228
+ generate_confirmation_token && save(validate: false)
229
+ end
230
+
231
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
232
+ @reconfirmation_required = true
233
+ self.unconfirmed_email = self.email
234
+ self.email = self.email_was
235
+ generate_confirmation_token
236
+ end
237
+
238
+ def postpone_email_change?
239
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && !self.email.blank?
240
+ @bypass_confirmation_postpone = false
241
+ postpone
242
+ end
243
+
244
+ def reconfirmation_required?
245
+ self.class.reconfirmable && @reconfirmation_required && !self.email.blank?
246
+ end
247
+
248
+ def send_confirmation_notification?
249
+ confirmation_required? && !@skip_confirmation_notification && !self.email.blank?
250
+ end
251
+
252
+ def after_confirmation
253
+ end
254
+
255
+ module ClassMethods
256
+ # Attempt to find a user by its email. If a record is found, send new
257
+ # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
258
+ # field. If no user is found, returns a new user with an email not found error.
259
+ # Options must contain the user email
260
+ def send_confirmation_instructions(attributes={})
261
+ confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
262
+ unless confirmable.try(:persisted?)
263
+ confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
264
+ end
265
+ confirmable.resend_confirmation_instructions if confirmable.persisted?
266
+ confirmable
267
+ end
268
+
269
+ # Find a user by its confirmation token and try to confirm it.
270
+ # If no user is found, returns a new user with an error.
271
+ # If the user is already confirmed, create an error for the user
272
+ # Options must have the confirmation_token
273
+ def confirm_by_token(confirmation_token)
274
+ original_token = confirmation_token
275
+ confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
276
+
277
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
278
+ confirmable.confirm! if confirmable.persisted?
279
+ confirmable.confirmation_token = original_token
280
+ confirmable
281
+ end
282
+
283
+ # Find a record for confirmation by unconfirmed email field
284
+ def find_by_unconfirmed_email_with_errors(attributes = {})
285
+ unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
286
+ unconfirmed_attributes = attributes.symbolize_keys
287
+ unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
288
+ find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
289
+ end
290
+
291
+ Devise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
292
+ end
293
+ end
294
+ end
295
+ end