deviseOne 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +38 -0
  4. data/.yardopts +9 -0
  5. data/CHANGELOG.md +1117 -0
  6. data/CONTRIBUTING.md +14 -0
  7. data/Gemfile +29 -0
  8. data/Gemfile.lock +199 -0
  9. data/MIT-LICENSE +20 -0
  10. data/README.md +529 -0
  11. data/Rakefile +35 -0
  12. data/app/controllers/devise/confirmations_controller.rb +47 -0
  13. data/app/controllers/devise/omniauth_callbacks_controller.rb +30 -0
  14. data/app/controllers/devise/passwords_controller.rb +71 -0
  15. data/app/controllers/devise/registrations_controller.rb +143 -0
  16. data/app/controllers/devise/sessions_controller.rb +166 -0
  17. data/app/controllers/devise/unlocks_controller.rb +46 -0
  18. data/app/controllers/devise_controller.rb +193 -0
  19. data/app/helpers/devise_helper.rb +25 -0
  20. data/app/mailers/devise/mailer.rb +20 -0
  21. data/app/views/devise/confirmations/new.html.erb +16 -0
  22. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  23. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  24. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  25. data/app/views/devise/passwords/edit.html.erb +25 -0
  26. data/app/views/devise/passwords/new.html.erb +16 -0
  27. data/app/views/devise/registrations/edit.html.erb +39 -0
  28. data/app/views/devise/registrations/new.html.erb +29 -0
  29. data/app/views/devise/sessions/new.html.erb +27 -0
  30. data/app/views/devise/shared/_links.html.erb +21 -0
  31. data/app/views/devise/unlocks/new.html.erb +16 -0
  32. data/config/locales/en.yml +70 -0
  33. data/devise.gemspec +33 -0
  34. data/devise.png +0 -0
  35. data/gemfiles/Gemfile.rails-3.2-stable +29 -0
  36. data/gemfiles/Gemfile.rails-3.2-stable.lock +169 -0
  37. data/gemfiles/Gemfile.rails-4.0-stable +29 -0
  38. data/gemfiles/Gemfile.rails-4.0-stable.lock +165 -0
  39. data/gemfiles/Gemfile.rails-4.1-stable +29 -0
  40. data/gemfiles/Gemfile.rails-4.1-stable.lock +170 -0
  41. data/lib/devise.rb +499 -0
  42. data/lib/devise/controllers/helpers.rb +284 -0
  43. data/lib/devise/controllers/rememberable.rb +47 -0
  44. data/lib/devise/controllers/scoped_views.rb +17 -0
  45. data/lib/devise/controllers/sign_in_out.rb +102 -0
  46. data/lib/devise/controllers/store_location.rb +58 -0
  47. data/lib/devise/controllers/url_helpers.rb +69 -0
  48. data/lib/devise/delegator.rb +16 -0
  49. data/lib/devise/failure_app.rb +212 -0
  50. data/lib/devise/hooks/activatable.rb +10 -0
  51. data/lib/devise/hooks/csrf_cleaner.rb +7 -0
  52. data/lib/devise/hooks/forgetable.rb +9 -0
  53. data/lib/devise/hooks/lockable.rb +7 -0
  54. data/lib/devise/hooks/proxy.rb +21 -0
  55. data/lib/devise/hooks/rememberable.rb +7 -0
  56. data/lib/devise/hooks/timeoutable.rb +35 -0
  57. data/lib/devise/hooks/trackable.rb +9 -0
  58. data/lib/devise/mailers/helpers.rb +90 -0
  59. data/lib/devise/mapping.rb +175 -0
  60. data/lib/devise/models.rb +119 -0
  61. data/lib/devise/models/authenticatable.rb +290 -0
  62. data/lib/devise/models/confirmable.rb +305 -0
  63. data/lib/devise/models/database_authenticatable.rb +164 -0
  64. data/lib/devise/models/lockable.rb +196 -0
  65. data/lib/devise/models/omniauthable.rb +27 -0
  66. data/lib/devise/models/recoverable.rb +157 -0
  67. data/lib/devise/models/registerable.rb +25 -0
  68. data/lib/devise/models/rememberable.rb +142 -0
  69. data/lib/devise/models/timeoutable.rb +49 -0
  70. data/lib/devise/models/trackable.rb +38 -0
  71. data/lib/devise/models/validatable.rb +66 -0
  72. data/lib/devise/modules.rb +28 -0
  73. data/lib/devise/omniauth.rb +28 -0
  74. data/lib/devise/omniauth/config.rb +45 -0
  75. data/lib/devise/omniauth/url_helpers.rb +18 -0
  76. data/lib/devise/orm/active_record.rb +3 -0
  77. data/lib/devise/orm/mongoid.rb +3 -0
  78. data/lib/devise/parameter_filter.rb +40 -0
  79. data/lib/devise/parameter_sanitizer.rb +99 -0
  80. data/lib/devise/rails.rb +56 -0
  81. data/lib/devise/rails/routes.rb +495 -0
  82. data/lib/devise/rails/warden_compat.rb +22 -0
  83. data/lib/devise/strategies/authenticatable.rb +173 -0
  84. data/lib/devise/strategies/base.rb +20 -0
  85. data/lib/devise/strategies/database_authenticatable.rb +24 -0
  86. data/lib/devise/strategies/rememberable.rb +59 -0
  87. data/lib/devise/test_helpers.rb +132 -0
  88. data/lib/devise/time_inflector.rb +14 -0
  89. data/lib/devise/token_generator.rb +70 -0
  90. data/lib/devise/version.rb +3 -0
  91. data/lib/generators/active_record/devise_generator.rb +91 -0
  92. data/lib/generators/active_record/templates/migration.rb +18 -0
  93. data/lib/generators/active_record/templates/migration_existing.rb +25 -0
  94. data/lib/generators/devise/controllers_generator.rb +44 -0
  95. data/lib/generators/devise/devise_generator.rb +26 -0
  96. data/lib/generators/devise/install_generator.rb +29 -0
  97. data/lib/generators/devise/orm_helpers.rb +51 -0
  98. data/lib/generators/devise/views_generator.rb +135 -0
  99. data/lib/generators/mongoid/devise_generator.rb +55 -0
  100. data/lib/generators/templates/README +35 -0
  101. data/lib/generators/templates/controllers/README +14 -0
  102. data/lib/generators/templates/controllers/confirmations_controller.rb +28 -0
  103. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +28 -0
  104. data/lib/generators/templates/controllers/passwords_controller.rb +32 -0
  105. data/lib/generators/templates/controllers/registrations_controller.rb +60 -0
  106. data/lib/generators/templates/controllers/sessions_controller.rb +25 -0
  107. data/lib/generators/templates/controllers/unlocks_controller.rb +28 -0
  108. data/lib/generators/templates/devise.rb +263 -0
  109. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  110. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  111. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  112. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +16 -0
  113. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +19 -0
  114. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +15 -0
  115. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +27 -0
  116. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +17 -0
  117. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +15 -0
  118. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +16 -0
  119. data/script/cached-bundle +49 -0
  120. data/script/s3-put +71 -0
  121. data/test/controllers/custom_registrations_controller_test.rb +35 -0
  122. data/test/controllers/custom_strategy_test.rb +62 -0
  123. data/test/controllers/helpers_test.rb +316 -0
  124. data/test/controllers/internal_helpers_test.rb +129 -0
  125. data/test/controllers/load_hooks_controller_test.rb +19 -0
  126. data/test/controllers/passwords_controller_test.rb +31 -0
  127. data/test/controllers/sessions_controller_test.rb +102 -0
  128. data/test/controllers/url_helpers_test.rb +65 -0
  129. data/test/delegator_test.rb +19 -0
  130. data/test/devise_test.rb +107 -0
  131. data/test/failure_app_test.rb +275 -0
  132. data/test/generators/active_record_generator_test.rb +109 -0
  133. data/test/generators/controllers_generator_test.rb +48 -0
  134. data/test/generators/devise_generator_test.rb +39 -0
  135. data/test/generators/install_generator_test.rb +13 -0
  136. data/test/generators/mongoid_generator_test.rb +23 -0
  137. data/test/generators/views_generator_test.rb +96 -0
  138. data/test/helpers/devise_helper_test.rb +49 -0
  139. data/test/integration/authenticatable_test.rb +731 -0
  140. data/test/integration/confirmable_test.rb +324 -0
  141. data/test/integration/database_authenticatable_test.rb +94 -0
  142. data/test/integration/http_authenticatable_test.rb +105 -0
  143. data/test/integration/lockable_test.rb +239 -0
  144. data/test/integration/omniauthable_test.rb +133 -0
  145. data/test/integration/recoverable_test.rb +334 -0
  146. data/test/integration/registerable_test.rb +361 -0
  147. data/test/integration/rememberable_test.rb +176 -0
  148. data/test/integration/timeoutable_test.rb +189 -0
  149. data/test/integration/trackable_test.rb +92 -0
  150. data/test/mailers/confirmation_instructions_test.rb +115 -0
  151. data/test/mailers/reset_password_instructions_test.rb +96 -0
  152. data/test/mailers/unlock_instructions_test.rb +91 -0
  153. data/test/mapping_test.rb +128 -0
  154. data/test/models/authenticatable_test.rb +23 -0
  155. data/test/models/confirmable_test.rb +461 -0
  156. data/test/models/database_authenticatable_test.rb +249 -0
  157. data/test/models/lockable_test.rb +328 -0
  158. data/test/models/omniauthable_test.rb +7 -0
  159. data/test/models/recoverable_test.rb +205 -0
  160. data/test/models/registerable_test.rb +7 -0
  161. data/test/models/rememberable_test.rb +198 -0
  162. data/test/models/serializable_test.rb +49 -0
  163. data/test/models/timeoutable_test.rb +51 -0
  164. data/test/models/trackable_test.rb +41 -0
  165. data/test/models/validatable_test.rb +127 -0
  166. data/test/models_test.rb +144 -0
  167. data/test/omniauth/config_test.rb +57 -0
  168. data/test/omniauth/url_helpers_test.rb +54 -0
  169. data/test/orm/active_record.rb +10 -0
  170. data/test/orm/mongoid.rb +13 -0
  171. data/test/parameter_sanitizer_test.rb +81 -0
  172. data/test/rails_app/Rakefile +6 -0
  173. data/test/rails_app/app/active_record/admin.rb +6 -0
  174. data/test/rails_app/app/active_record/shim.rb +2 -0
  175. data/test/rails_app/app/active_record/user.rb +6 -0
  176. data/test/rails_app/app/active_record/user_on_engine.rb +7 -0
  177. data/test/rails_app/app/active_record/user_on_main_app.rb +7 -0
  178. data/test/rails_app/app/controllers/admins/sessions_controller.rb +6 -0
  179. data/test/rails_app/app/controllers/admins_controller.rb +11 -0
  180. data/test/rails_app/app/controllers/application_controller.rb +12 -0
  181. data/test/rails_app/app/controllers/application_with_fake_engine.rb +30 -0
  182. data/test/rails_app/app/controllers/custom/registrations_controller.rb +21 -0
  183. data/test/rails_app/app/controllers/home_controller.rb +25 -0
  184. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +2 -0
  185. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +2 -0
  186. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +14 -0
  187. data/test/rails_app/app/controllers/users_controller.rb +31 -0
  188. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  189. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +3 -0
  190. data/test/rails_app/app/mailers/users/mailer.rb +3 -0
  191. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +4 -0
  192. data/test/rails_app/app/mongoid/admin.rb +29 -0
  193. data/test/rails_app/app/mongoid/shim.rb +23 -0
  194. data/test/rails_app/app/mongoid/user.rb +39 -0
  195. data/test/rails_app/app/mongoid/user_on_engine.rb +39 -0
  196. data/test/rails_app/app/mongoid/user_on_main_app.rb +39 -0
  197. data/test/rails_app/app/views/admins/index.html.erb +1 -0
  198. data/test/rails_app/app/views/admins/sessions/new.html.erb +2 -0
  199. data/test/rails_app/app/views/home/admin_dashboard.html.erb +1 -0
  200. data/test/rails_app/app/views/home/index.html.erb +1 -0
  201. data/test/rails_app/app/views/home/join.html.erb +1 -0
  202. data/test/rails_app/app/views/home/private.html.erb +1 -0
  203. data/test/rails_app/app/views/home/user_dashboard.html.erb +1 -0
  204. data/test/rails_app/app/views/layouts/application.html.erb +24 -0
  205. data/test/rails_app/app/views/users/edit_form.html.erb +1 -0
  206. data/test/rails_app/app/views/users/index.html.erb +1 -0
  207. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +1 -0
  208. data/test/rails_app/app/views/users/sessions/new.html.erb +1 -0
  209. data/test/rails_app/bin/bundle +3 -0
  210. data/test/rails_app/bin/rails +4 -0
  211. data/test/rails_app/bin/rake +4 -0
  212. data/test/rails_app/config.ru +4 -0
  213. data/test/rails_app/config/application.rb +40 -0
  214. data/test/rails_app/config/boot.rb +14 -0
  215. data/test/rails_app/config/database.yml +18 -0
  216. data/test/rails_app/config/environment.rb +5 -0
  217. data/test/rails_app/config/environments/development.rb +30 -0
  218. data/test/rails_app/config/environments/production.rb +80 -0
  219. data/test/rails_app/config/environments/test.rb +36 -0
  220. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  221. data/test/rails_app/config/initializers/devise.rb +180 -0
  222. data/test/rails_app/config/initializers/inflections.rb +2 -0
  223. data/test/rails_app/config/initializers/secret_token.rb +8 -0
  224. data/test/rails_app/config/initializers/session_store.rb +1 -0
  225. data/test/rails_app/config/routes.rb +122 -0
  226. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +71 -0
  227. data/test/rails_app/db/schema.rb +55 -0
  228. data/test/rails_app/lib/shared_admin.rb +17 -0
  229. data/test/rails_app/lib/shared_user.rb +29 -0
  230. data/test/rails_app/lib/shared_user_without_omniauth.rb +13 -0
  231. data/test/rails_app/public/404.html +26 -0
  232. data/test/rails_app/public/422.html +26 -0
  233. data/test/rails_app/public/500.html +26 -0
  234. data/test/rails_app/public/favicon.ico +0 -0
  235. data/test/routes_test.rb +264 -0
  236. data/test/support/action_controller/record_identifier.rb +10 -0
  237. data/test/support/assertions.rb +39 -0
  238. data/test/support/helpers.rb +73 -0
  239. data/test/support/integration.rb +92 -0
  240. data/test/support/locale/en.yml +8 -0
  241. data/test/support/mongoid.yml +6 -0
  242. data/test/support/webrat/integrations/rails.rb +24 -0
  243. data/test/test_helper.rb +34 -0
  244. data/test/test_helpers_test.rb +163 -0
  245. data/test/test_models.rb +33 -0
  246. metadata +531 -0
@@ -0,0 +1,305 @@
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!(args={})
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: args[:ensure_valid] == true)
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.present?
240
+ @bypass_confirmation_postpone = false
241
+ postpone
242
+ end
243
+
244
+ def reconfirmation_required?
245
+ self.class.reconfirmable && @reconfirmation_required && self.email.present?
246
+ end
247
+
248
+ def send_confirmation_notification?
249
+ confirmation_required? && !@skip_confirmation_notification && self.email.present?
250
+ end
251
+
252
+ # A callback initiated after successfully confirming. This can be
253
+ # used to insert your own logic that is only run after the user successfully
254
+ # confirms.
255
+ #
256
+ # Example:
257
+ #
258
+ # def after_confirmation
259
+ # self.update_attribute(:invite_code, nil)
260
+ # end
261
+ #
262
+ def after_confirmation
263
+ end
264
+
265
+ module ClassMethods
266
+ # Attempt to find a user by its email. If a record is found, send new
267
+ # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
268
+ # field. If no user is found, returns a new user with an email not found error.
269
+ # Options must contain the user email
270
+ def send_confirmation_instructions(attributes={})
271
+ confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
272
+ unless confirmable.try(:persisted?)
273
+ confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
274
+ end
275
+ confirmable.resend_confirmation_instructions if confirmable.persisted?
276
+ confirmable
277
+ end
278
+
279
+ # Find a user by its confirmation token and try to confirm it.
280
+ # If no user is found, returns a new user with an error.
281
+ # If the user is already confirmed, create an error for the user
282
+ # Options must have the confirmation_token
283
+ def confirm_by_token(confirmation_token)
284
+ original_token = confirmation_token
285
+ confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
286
+
287
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
288
+ confirmable.confirm! if confirmable.persisted?
289
+ confirmable.confirmation_token = original_token
290
+ confirmable
291
+ end
292
+
293
+ # Find a record for confirmation by unconfirmed email field
294
+ def find_by_unconfirmed_email_with_errors(attributes = {})
295
+ unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
296
+ unconfirmed_attributes = attributes.symbolize_keys
297
+ unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
298
+ find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
299
+ end
300
+
301
+ Devise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
302
+ end
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,164 @@
1
+ require 'devise/strategies/database_authenticatable'
2
+ require 'bcrypt'
3
+
4
+ module Devise
5
+ # Digests the password using bcrypt.
6
+ def self.bcrypt(klass, password)
7
+ ::BCrypt::Password.create("#{password}#{klass.pepper}", cost: klass.stretches).to_s
8
+ end
9
+
10
+ module Models
11
+ # Authenticatable Module, responsible for encrypting password and validating
12
+ # authenticity of a user while signing in.
13
+ #
14
+ # == Options
15
+ #
16
+ # DatabaseAuthenticable adds the following options to devise_for:
17
+ #
18
+ # * +pepper+: a random string used to provide a more secure hash. Use
19
+ # `rake secret` to generate new keys.
20
+ #
21
+ # * +stretches+: the cost given to bcrypt.
22
+ #
23
+ # == Examples
24
+ #
25
+ # User.find(1).valid_password?('password123') # returns true/false
26
+ #
27
+ module DatabaseAuthenticatable
28
+ extend ActiveSupport::Concern
29
+
30
+ included do
31
+ attr_reader :password, :current_password
32
+ attr_accessor :password_confirmation
33
+ end
34
+
35
+ def self.required_fields(klass)
36
+ [:encrypted_password] + klass.authentication_keys
37
+ end
38
+
39
+ # Generates password encryption based on the given value.
40
+ def password=(new_password)
41
+ @password = new_password
42
+ self.encrypted_password = password_digest(@password) if @password.present?
43
+ end
44
+
45
+ # Verifies whether a password (ie from sign in) is the user password.
46
+ def valid_password?(password)
47
+ return false if encrypted_password.blank?
48
+ bcrypt = ::BCrypt::Password.new(encrypted_password)
49
+ password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
50
+ Devise.secure_compare(password, encrypted_password)
51
+ end
52
+
53
+ # Set password and password confirmation to nil
54
+ def clean_up_passwords
55
+ self.password = self.password_confirmation = nil
56
+ end
57
+
58
+ # Update record attributes when :current_password matches, otherwise
59
+ # returns error on :current_password.
60
+ #
61
+ # This method also rejects the password field if it is blank (allowing
62
+ # users to change relevant information like the e-mail without changing
63
+ # their password). In case the password field is rejected, the confirmation
64
+ # is also rejected as long as it is also blank.
65
+ def update_with_password(params, *options)
66
+ current_password = params.delete(:current_password)
67
+
68
+ if params[:password].blank?
69
+ params.delete(:password)
70
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
71
+ end
72
+
73
+ result = if valid_password?(current_password)
74
+ update_attributes(params, *options)
75
+ else
76
+ self.assign_attributes(params, *options)
77
+ self.valid?
78
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
79
+ false
80
+ end
81
+
82
+ clean_up_passwords
83
+ result
84
+ end
85
+
86
+ # Updates record attributes without asking for the current password.
87
+ # Never allows a change to the current password. If you are using this
88
+ # method, you should probably override this method to protect other
89
+ # attributes you would not like to be updated without a password.
90
+ #
91
+ # Example:
92
+ #
93
+ # def update_without_password(params, *options)
94
+ # params.delete(:email)
95
+ # super(params)
96
+ # end
97
+ #
98
+ def update_without_password(params, *options)
99
+ params.delete(:password)
100
+ params.delete(:password_confirmation)
101
+
102
+ result = update_attributes(params, *options)
103
+ clean_up_passwords
104
+ result
105
+ end
106
+
107
+ # Destroy record when :current_password matches, otherwise returns
108
+ # error on :current_password. It also automatically rejects
109
+ # :current_password if it is blank.
110
+ def destroy_with_password(current_password)
111
+ result = if valid_password?(current_password)
112
+ destroy
113
+ else
114
+ self.valid?
115
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
116
+ false
117
+ end
118
+
119
+ result
120
+ end
121
+
122
+ # A callback initiated after successfully authenticating. This can be
123
+ # used to insert your own logic that is only run after the user successfully
124
+ # authenticates.
125
+ #
126
+ # Example:
127
+ #
128
+ # def after_database_authentication
129
+ # self.update_attribute(:invite_code, nil)
130
+ # end
131
+ #
132
+ def after_database_authentication
133
+ end
134
+
135
+ # A reliable way to expose the salt regardless of the implementation.
136
+ def authenticatable_salt
137
+ encrypted_password[0,29] if encrypted_password
138
+ end
139
+
140
+ protected
141
+
142
+ # Digests the password using bcrypt. Custom encryption should override
143
+ # this method to apply their own algorithm.
144
+ #
145
+ # See https://github.com/plataformatec/devise-encryptable for examples
146
+ # of other encryption engines.
147
+ def password_digest(password)
148
+ Devise.bcrypt(self.class, password)
149
+ end
150
+
151
+ module ClassMethods
152
+ Devise::Models.config(self, :pepper, :stretches)
153
+
154
+ # We assume this method already gets the sanitized values from the
155
+ # DatabaseAuthenticatable strategy. If you are using this method on
156
+ # your own, be sure to sanitize the conditions hash to only include
157
+ # the proper fields.
158
+ def find_for_database_authentication(conditions)
159
+ find_for_authentication(conditions)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end