/lib/devise/models/confirmable.rb

https://github.com/lest/devise · Ruby · 237 lines · 134 code · 29 blank · 74 comment · 21 complexity · 2e27a9dc8b358558979041107122bff9 MD5 · raw file

  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_for:
  11. #
  12. # * +confirm_within+: the time you want to allow the user to access his 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 confirm_within 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. #
  23. # == Examples
  24. #
  25. # User.find(1).confirm! # returns true unless it's already confirmed
  26. # User.find(1).confirmed? # true/false
  27. # User.find(1).send_confirmation_instructions # manually send instructions
  28. #
  29. module Confirmable
  30. extend ActiveSupport::Concern
  31. # email uniqueness validation in unconfirmed_email column, works only if unconfirmed_email is defined on record
  32. class ConfirmableValidator < ActiveModel::Validator
  33. def validate(record)
  34. if unconfirmed_email_defined?(record) && email_exists_in_unconfirmed_emails?(record)
  35. record.errors.add(:email, :taken)
  36. end
  37. end
  38. protected
  39. def unconfirmed_email_defined?(record)
  40. record.respond_to?(:unconfirmed_email)
  41. end
  42. def email_exists_in_unconfirmed_emails?(record)
  43. count = record.class.where(:unconfirmed_email => record.email).count
  44. expected_count = record.new_record? ? 0 : 1
  45. count > expected_count
  46. end
  47. end
  48. included do
  49. before_create :generate_confirmation_token, :if => :confirmation_required?
  50. after_create :send_confirmation_instructions, :if => :confirmation_required?
  51. before_update :postpone_email_change_until_confirmation, :if => :postpone_email_change?
  52. after_update :send_confirmation_instructions, :if => :email_change_confirmation_required?
  53. end
  54. # Confirm a user by setting it's confirmed_at to actual time. If the user
  55. # is already confirmed, add en error to email field. If the user is invalid
  56. # add errors
  57. def confirm!
  58. unless_confirmed do
  59. self.confirmation_token = nil
  60. self.confirmed_at = Time.now
  61. if self.class.reconfirmable
  62. @bypass_postpone = true
  63. self.email = unconfirmed_email if unconfirmed_email.present?
  64. self.unconfirmed_email = nil
  65. save
  66. else
  67. save(:validate => false)
  68. end
  69. end
  70. end
  71. # Verifies whether a user is confirmed or not
  72. def confirmed?
  73. !!confirmed_at
  74. end
  75. # Send confirmation instructions by email
  76. def send_confirmation_instructions
  77. @email_change_confirmation_required = false
  78. generate_confirmation_token! if self.confirmation_token.nil?
  79. ::Devise.mailer.confirmation_instructions(self).deliver
  80. end
  81. # Resend confirmation token. This method does not need to generate a new token.
  82. def resend_confirmation_token
  83. unless_confirmed { send_confirmation_instructions }
  84. end
  85. # Overwrites active_for_authentication? for confirmation
  86. # by verifying whether a user is active to sign in or not. If the user
  87. # is already confirmed, it should never be blocked. Otherwise we need to
  88. # calculate if the confirm time has not expired for this user.
  89. def active_for_authentication?
  90. super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
  91. end
  92. # The message to be shown if the account is inactive.
  93. def inactive_message
  94. !confirmed? ? :unconfirmed : super
  95. end
  96. # If you don't want confirmation to be sent on create, neither a code
  97. # to be generated, call skip_confirmation!
  98. def skip_confirmation!
  99. self.confirmed_at = Time.now
  100. end
  101. def headers_for(action)
  102. if action == :confirmation_instructions && respond_to?(:unconfirmed_email)
  103. { :to => unconfirmed_email.present? ? unconfirmed_email : email }
  104. else
  105. {}
  106. end
  107. end
  108. protected
  109. # Callback to overwrite if confirmation is required or not.
  110. def confirmation_required?
  111. !confirmed?
  112. end
  113. # Checks if the confirmation for the user is within the limit time.
  114. # We do this by calculating if the difference between today and the
  115. # confirmation sent date does not exceed the confirm in time configured.
  116. # Confirm_within is a model configuration, must always be an integer value.
  117. #
  118. # Example:
  119. #
  120. # # confirm_within = 1.day and confirmation_sent_at = today
  121. # confirmation_period_valid? # returns true
  122. #
  123. # # confirm_within = 5.days and confirmation_sent_at = 4.days.ago
  124. # confirmation_period_valid? # returns true
  125. #
  126. # # confirm_within = 5.days and confirmation_sent_at = 5.days.ago
  127. # confirmation_period_valid? # returns false
  128. #
  129. # # confirm_within = 0.days
  130. # confirmation_period_valid? # will always return false
  131. #
  132. def confirmation_period_valid?
  133. confirmation_sent_at && confirmation_sent_at.utc >= self.class.confirm_within.ago
  134. end
  135. # Checks whether the record is confirmed or not or a new email has been added, yielding to the block
  136. # if it's already confirmed, otherwise adds an error to email.
  137. def unless_confirmed
  138. unless confirmed? && (self.class.reconfirmable ? unconfirmed_email.blank? : true)
  139. yield
  140. else
  141. self.errors.add(:email, :already_confirmed)
  142. false
  143. end
  144. end
  145. # Generates a new random token for confirmation, and stores the time
  146. # this token is being generated
  147. def generate_confirmation_token
  148. self.confirmation_token = self.class.confirmation_token
  149. self.confirmation_sent_at = Time.now.utc
  150. end
  151. def generate_confirmation_token!
  152. generate_confirmation_token && save(:validate => false)
  153. end
  154. def after_password_reset
  155. super
  156. confirm! unless confirmed?
  157. end
  158. def postpone_email_change_until_confirmation
  159. @email_change_confirmation_required = true
  160. self.unconfirmed_email = self.email
  161. self.email = self.email_was
  162. end
  163. def postpone_email_change?
  164. postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone
  165. @bypass_postpone = nil
  166. postpone
  167. end
  168. def email_change_confirmation_required?
  169. self.class.reconfirmable && @email_change_confirmation_required
  170. end
  171. module ClassMethods
  172. # Attempt to find a user by its email. If a record is found, send new
  173. # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
  174. # field. If no user is found, returns a new user with an email not found error.
  175. # Options must contain the user email
  176. def send_confirmation_instructions(attributes={})
  177. confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
  178. unless confirmable.try(:persisted?)
  179. confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
  180. end
  181. confirmable.resend_confirmation_token if confirmable.persisted?
  182. confirmable
  183. end
  184. # Find a user by its confirmation token and try to confirm it.
  185. # If no user is found, returns a new user with an error.
  186. # If the user is already confirmed, create an error for the user
  187. # Options must have the confirmation_token
  188. def confirm_by_token(confirmation_token)
  189. confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
  190. confirmable.confirm! if confirmable.persisted?
  191. confirmable
  192. end
  193. # Generate a token checking if one does not already exist in the database.
  194. def confirmation_token
  195. generate_token(:confirmation_token)
  196. end
  197. # Find a record for confirmation by unconfirmed email field
  198. def find_by_unconfirmed_email_with_errors(attributes = {})
  199. unconfirmed_required_attributes = confirmation_keys.map{ |k| k == :email ? :unconfirmed_email : k }
  200. unconfirmed_attributes = attributes.symbolize_keys
  201. unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
  202. find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
  203. end
  204. Devise::Models.config(self, :confirm_within, :confirmation_keys, :reconfirmable)
  205. end
  206. end
  207. end
  208. end