devise-warbler 2.2.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 (208) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +35 -0
  3. data/CHANGELOG.rdoc +923 -0
  4. data/CONTRIBUTING.md +14 -0
  5. data/Gemfile +32 -0
  6. data/Gemfile.lock +156 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +396 -0
  9. data/Rakefile +34 -0
  10. data/app/controllers/devise/confirmations_controller.rb +43 -0
  11. data/app/controllers/devise/omniauth_callbacks_controller.rb +30 -0
  12. data/app/controllers/devise/passwords_controller.rb +65 -0
  13. data/app/controllers/devise/registrations_controller.rb +119 -0
  14. data/app/controllers/devise/sessions_controller.rb +48 -0
  15. data/app/controllers/devise/unlocks_controller.rb +44 -0
  16. data/app/controllers/devise_controller.rb +184 -0
  17. data/app/helpers/devise_helper.rb +25 -0
  18. data/app/mailers/devise/mailer.rb +15 -0
  19. data/app/views/devise/_links.erb +3 -0
  20. data/app/views/devise/confirmations/new.html.erb +12 -0
  21. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  22. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  23. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  24. data/app/views/devise/passwords/edit.html.erb +16 -0
  25. data/app/views/devise/passwords/new.html.erb +12 -0
  26. data/app/views/devise/registrations/edit.html.erb +29 -0
  27. data/app/views/devise/registrations/new.html.erb +18 -0
  28. data/app/views/devise/sessions/new.html.erb +17 -0
  29. data/app/views/devise/shared/_links.erb +25 -0
  30. data/app/views/devise/unlocks/new.html.erb +12 -0
  31. data/config/locales/en.yml +59 -0
  32. data/devise.gemspec +24 -0
  33. data/gemfiles/Gemfile.rails-3.1.x +35 -0
  34. data/gemfiles/Gemfile.rails-3.1.x.lock +167 -0
  35. data/lib/devise.rb +451 -0
  36. data/lib/devise/controllers/helpers.rb +285 -0
  37. data/lib/devise/controllers/rememberable.rb +56 -0
  38. data/lib/devise/controllers/scoped_views.rb +17 -0
  39. data/lib/devise/controllers/url_helpers.rb +67 -0
  40. data/lib/devise/delegator.rb +16 -0
  41. data/lib/devise/failure_app.rb +187 -0
  42. data/lib/devise/hooks/activatable.rb +11 -0
  43. data/lib/devise/hooks/forgetable.rb +9 -0
  44. data/lib/devise/hooks/lockable.rb +7 -0
  45. data/lib/devise/hooks/rememberable.rb +6 -0
  46. data/lib/devise/hooks/timeoutable.rb +25 -0
  47. data/lib/devise/hooks/trackable.rb +9 -0
  48. data/lib/devise/mailers/helpers.rb +95 -0
  49. data/lib/devise/mapping.rb +172 -0
  50. data/lib/devise/models.rb +128 -0
  51. data/lib/devise/models/authenticatable.rb +276 -0
  52. data/lib/devise/models/confirmable.rb +267 -0
  53. data/lib/devise/models/database_authenticatable.rb +126 -0
  54. data/lib/devise/models/lockable.rb +193 -0
  55. data/lib/devise/models/omniauthable.rb +27 -0
  56. data/lib/devise/models/recoverable.rb +140 -0
  57. data/lib/devise/models/registerable.rb +25 -0
  58. data/lib/devise/models/rememberable.rb +125 -0
  59. data/lib/devise/models/timeoutable.rb +49 -0
  60. data/lib/devise/models/token_authenticatable.rb +89 -0
  61. data/lib/devise/models/trackable.rb +35 -0
  62. data/lib/devise/models/validatable.rb +66 -0
  63. data/lib/devise/modules.rb +29 -0
  64. data/lib/devise/omniauth.rb +28 -0
  65. data/lib/devise/omniauth/config.rb +45 -0
  66. data/lib/devise/omniauth/url_helpers.rb +18 -0
  67. data/lib/devise/orm/active_record.rb +3 -0
  68. data/lib/devise/orm/mongoid.rb +3 -0
  69. data/lib/devise/param_filter.rb +40 -0
  70. data/lib/devise/rails.rb +51 -0
  71. data/lib/devise/rails/routes.rb +448 -0
  72. data/lib/devise/rails/warden_compat.rb +43 -0
  73. data/lib/devise/strategies/authenticatable.rb +176 -0
  74. data/lib/devise/strategies/base.rb +20 -0
  75. data/lib/devise/strategies/database_authenticatable.rb +20 -0
  76. data/lib/devise/strategies/rememberable.rb +55 -0
  77. data/lib/devise/strategies/token_authenticatable.rb +56 -0
  78. data/lib/devise/test_helpers.rb +131 -0
  79. data/lib/devise/time_inflector.rb +14 -0
  80. data/lib/devise/version.rb +3 -0
  81. data/lib/generators/active_record/devise_generator.rb +79 -0
  82. data/lib/generators/active_record/templates/migration.rb +19 -0
  83. data/lib/generators/active_record/templates/migration_existing.rb +26 -0
  84. data/lib/generators/devise/devise_generator.rb +24 -0
  85. data/lib/generators/devise/install_generator.rb +24 -0
  86. data/lib/generators/devise/orm_helpers.rb +32 -0
  87. data/lib/generators/devise/views_generator.rb +122 -0
  88. data/lib/generators/mongoid/devise_generator.rb +57 -0
  89. data/lib/generators/templates/README +35 -0
  90. data/lib/generators/templates/devise.rb +240 -0
  91. data/lib/generators/templates/markerb/confirmation_instructions.markerb +5 -0
  92. data/lib/generators/templates/markerb/reset_password_instructions.markerb +8 -0
  93. data/lib/generators/templates/markerb/unlock_instructions.markerb +7 -0
  94. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +16 -0
  95. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +19 -0
  96. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +15 -0
  97. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +27 -0
  98. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +17 -0
  99. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +15 -0
  100. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +16 -0
  101. data/test/controllers/custom_strategy_test.rb +62 -0
  102. data/test/controllers/helpers_test.rb +253 -0
  103. data/test/controllers/internal_helpers_test.rb +110 -0
  104. data/test/controllers/sessions_controller_test.rb +85 -0
  105. data/test/controllers/url_helpers_test.rb +59 -0
  106. data/test/delegator_test.rb +19 -0
  107. data/test/devise_test.rb +83 -0
  108. data/test/failure_app_test.rb +221 -0
  109. data/test/generators/active_record_generator_test.rb +75 -0
  110. data/test/generators/devise_generator_test.rb +39 -0
  111. data/test/generators/install_generator_test.rb +13 -0
  112. data/test/generators/mongoid_generator_test.rb +23 -0
  113. data/test/generators/views_generator_test.rb +67 -0
  114. data/test/helpers/devise_helper_test.rb +51 -0
  115. data/test/integration/authenticatable_test.rb +687 -0
  116. data/test/integration/confirmable_test.rb +299 -0
  117. data/test/integration/database_authenticatable_test.rb +84 -0
  118. data/test/integration/http_authenticatable_test.rb +97 -0
  119. data/test/integration/lockable_test.rb +242 -0
  120. data/test/integration/omniauthable_test.rb +133 -0
  121. data/test/integration/recoverable_test.rb +334 -0
  122. data/test/integration/registerable_test.rb +347 -0
  123. data/test/integration/rememberable_test.rb +165 -0
  124. data/test/integration/timeoutable_test.rb +140 -0
  125. data/test/integration/token_authenticatable_test.rb +161 -0
  126. data/test/integration/trackable_test.rb +92 -0
  127. data/test/mailers/confirmation_instructions_test.rb +106 -0
  128. data/test/mailers/reset_password_instructions_test.rb +87 -0
  129. data/test/mailers/unlock_instructions_test.rb +82 -0
  130. data/test/mapping_test.rb +127 -0
  131. data/test/models/authenticatable_test.rb +13 -0
  132. data/test/models/confirmable_test.rb +398 -0
  133. data/test/models/database_authenticatable_test.rb +207 -0
  134. data/test/models/lockable_test.rb +273 -0
  135. data/test/models/omniauthable_test.rb +7 -0
  136. data/test/models/recoverable_test.rb +205 -0
  137. data/test/models/registerable_test.rb +7 -0
  138. data/test/models/rememberable_test.rb +174 -0
  139. data/test/models/serializable_test.rb +49 -0
  140. data/test/models/timeoutable_test.rb +46 -0
  141. data/test/models/token_authenticatable_test.rb +55 -0
  142. data/test/models/trackable_test.rb +13 -0
  143. data/test/models/validatable_test.rb +117 -0
  144. data/test/models_test.rb +158 -0
  145. data/test/omniauth/config_test.rb +57 -0
  146. data/test/omniauth/url_helpers_test.rb +51 -0
  147. data/test/orm/active_record.rb +9 -0
  148. data/test/orm/mongoid.rb +13 -0
  149. data/test/rails_app/Rakefile +10 -0
  150. data/test/rails_app/app/active_record/admin.rb +6 -0
  151. data/test/rails_app/app/active_record/shim.rb +2 -0
  152. data/test/rails_app/app/active_record/user.rb +6 -0
  153. data/test/rails_app/app/controllers/admins/sessions_controller.rb +6 -0
  154. data/test/rails_app/app/controllers/admins_controller.rb +11 -0
  155. data/test/rails_app/app/controllers/application_controller.rb +9 -0
  156. data/test/rails_app/app/controllers/home_controller.rb +25 -0
  157. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +2 -0
  158. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +2 -0
  159. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +14 -0
  160. data/test/rails_app/app/controllers/users_controller.rb +23 -0
  161. data/test/rails_app/app/helpers/application_helper.rb +3 -0
  162. data/test/rails_app/app/mailers/users/mailer.rb +8 -0
  163. data/test/rails_app/app/mongoid/admin.rb +29 -0
  164. data/test/rails_app/app/mongoid/shim.rb +24 -0
  165. data/test/rails_app/app/mongoid/user.rb +42 -0
  166. data/test/rails_app/app/views/admins/index.html.erb +1 -0
  167. data/test/rails_app/app/views/admins/sessions/new.html.erb +2 -0
  168. data/test/rails_app/app/views/home/admin_dashboard.html.erb +1 -0
  169. data/test/rails_app/app/views/home/index.html.erb +1 -0
  170. data/test/rails_app/app/views/home/join.html.erb +1 -0
  171. data/test/rails_app/app/views/home/private.html.erb +1 -0
  172. data/test/rails_app/app/views/home/user_dashboard.html.erb +1 -0
  173. data/test/rails_app/app/views/layouts/application.html.erb +24 -0
  174. data/test/rails_app/app/views/users/index.html.erb +1 -0
  175. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +1 -0
  176. data/test/rails_app/app/views/users/sessions/new.html.erb +1 -0
  177. data/test/rails_app/config.ru +4 -0
  178. data/test/rails_app/config/application.rb +41 -0
  179. data/test/rails_app/config/boot.rb +8 -0
  180. data/test/rails_app/config/database.yml +18 -0
  181. data/test/rails_app/config/environment.rb +5 -0
  182. data/test/rails_app/config/environments/development.rb +18 -0
  183. data/test/rails_app/config/environments/production.rb +33 -0
  184. data/test/rails_app/config/environments/test.rb +33 -0
  185. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  186. data/test/rails_app/config/initializers/devise.rb +178 -0
  187. data/test/rails_app/config/initializers/inflections.rb +2 -0
  188. data/test/rails_app/config/initializers/secret_token.rb +2 -0
  189. data/test/rails_app/config/routes.rb +100 -0
  190. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +74 -0
  191. data/test/rails_app/db/schema.rb +52 -0
  192. data/test/rails_app/lib/shared_admin.rb +14 -0
  193. data/test/rails_app/lib/shared_user.rb +26 -0
  194. data/test/rails_app/public/404.html +26 -0
  195. data/test/rails_app/public/422.html +26 -0
  196. data/test/rails_app/public/500.html +26 -0
  197. data/test/rails_app/public/favicon.ico +0 -0
  198. data/test/rails_app/script/rails +10 -0
  199. data/test/routes_test.rb +248 -0
  200. data/test/support/assertions.rb +40 -0
  201. data/test/support/helpers.rb +91 -0
  202. data/test/support/integration.rb +92 -0
  203. data/test/support/locale/en.yml +4 -0
  204. data/test/support/webrat/integrations/rails.rb +24 -0
  205. data/test/test_helper.rb +27 -0
  206. data/test/test_helpers_test.rb +151 -0
  207. data/test/test_models.rb +27 -0
  208. metadata +423 -0
@@ -0,0 +1,267 @@
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
+ # * +allow_unconfirmed_access_for+: 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 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 => :confirmation_required?
38
+ before_update :postpone_email_change_until_confirmation, :if => :postpone_email_change?
39
+ after_update :send_confirmation_instructions, :if => :reconfirmation_required?
40
+ end
41
+
42
+ def self.required_fields(klass)
43
+ required_methods = [:confirmation_token, :confirmed_at, :confirmation_sent_at]
44
+ required_methods << :unconfirmed_email if klass.reconfirmable
45
+ required_methods
46
+ end
47
+
48
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
49
+ # is already confirmed, add an error to email field. If the user is invalid
50
+ # add errors
51
+ def confirm!
52
+ pending_any_confirmation do
53
+ if confirmation_period_expired?
54
+ self.errors.add(:email, :confirmation_period_expired,
55
+ :period => Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
56
+ return false
57
+ end
58
+
59
+ self.confirmation_token = nil
60
+ self.confirmed_at = Time.now.utc
61
+
62
+ if self.class.reconfirmable && unconfirmed_email.present?
63
+ skip_reconfirmation!
64
+ self.email = unconfirmed_email
65
+ self.unconfirmed_email = nil
66
+
67
+ # We need to validate in such cases to enforce e-mail uniqueness
68
+ save(:validate => true)
69
+ else
70
+ save(:validate => false)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Verifies whether a user is confirmed or not
76
+ def confirmed?
77
+ !!confirmed_at
78
+ end
79
+
80
+ def pending_reconfirmation?
81
+ self.class.reconfirmable && unconfirmed_email.present?
82
+ end
83
+
84
+ # Send confirmation instructions by email
85
+ def send_confirmation_instructions
86
+ self.confirmation_token = nil if reconfirmation_required?
87
+ @reconfirmation_required = false
88
+
89
+ generate_confirmation_token! if self.confirmation_token.blank?
90
+
91
+ opts = pending_reconfirmation? ? { :to => unconfirmed_email } : { }
92
+ send_devise_notification(:confirmation_instructions, opts)
93
+ end
94
+
95
+ # Resend confirmation token. This method does not need to generate a new token.
96
+ def resend_confirmation_token
97
+ pending_any_confirmation do
98
+ self.confirmation_token = nil if confirmation_period_expired?
99
+ send_confirmation_instructions
100
+ end
101
+ end
102
+
103
+ # Overwrites active_for_authentication? for confirmation
104
+ # by verifying whether a user is active to sign in or not. If the user
105
+ # is already confirmed, it should never be blocked. Otherwise we need to
106
+ # calculate if the confirm time has not expired for this user.
107
+ def active_for_authentication?
108
+ super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
109
+ end
110
+
111
+ # The message to be shown if the account is inactive.
112
+ def inactive_message
113
+ !confirmed? ? :unconfirmed : super
114
+ end
115
+
116
+ # If you don't want confirmation to be sent on create, neither a code
117
+ # to be generated, call skip_confirmation!
118
+ def skip_confirmation!
119
+ self.confirmed_at = Time.now.utc
120
+ end
121
+
122
+ # If you don't want reconfirmation to be sent, neither a code
123
+ # to be generated, call skip_reconfirmation!
124
+ def skip_reconfirmation!
125
+ @bypass_postpone = true
126
+ end
127
+
128
+ protected
129
+
130
+ # A callback method used to deliver confirmation
131
+ # instructions on creation. This can be overriden
132
+ # in models to map to a nice sign up e-mail.
133
+ def send_on_create_confirmation_instructions
134
+ send_devise_notification(:confirmation_instructions)
135
+ end
136
+
137
+ # Callback to overwrite if confirmation is required or not.
138
+ def confirmation_required?
139
+ !confirmed?
140
+ end
141
+
142
+ # Checks if the confirmation for the user is within the limit time.
143
+ # We do this by calculating if the difference between today and the
144
+ # confirmation sent date does not exceed the confirm in time configured.
145
+ # Confirm_within is a model configuration, must always be an integer value.
146
+ #
147
+ # Example:
148
+ #
149
+ # # allow_unconfirmed_access_for = 1.day and confirmation_sent_at = today
150
+ # confirmation_period_valid? # returns true
151
+ #
152
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 4.days.ago
153
+ # confirmation_period_valid? # returns true
154
+ #
155
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 5.days.ago
156
+ # confirmation_period_valid? # returns false
157
+ #
158
+ # # allow_unconfirmed_access_for = 0.days
159
+ # confirmation_period_valid? # will always return false
160
+ #
161
+ # # allow_unconfirmed_access_for = nil
162
+ # confirmation_period_valid? # will always return true
163
+ #
164
+ def confirmation_period_valid?
165
+ self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
166
+ end
167
+
168
+ # Checks if the user confirmation happens before the token becomes invalid
169
+ # Examples:
170
+ #
171
+ # # confirm_within = 3.days and confirmation_sent_at = 2.days.ago
172
+ # confirmation_period_expired? # returns false
173
+ #
174
+ # # confirm_within = 3.days and confirmation_sent_at = 4.days.ago
175
+ # confirmation_period_expired? # returns true
176
+ #
177
+ # # confirm_within = nil
178
+ # confirmation_period_expired? # will always return false
179
+ #
180
+ def confirmation_period_expired?
181
+ self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
182
+ end
183
+
184
+ # Checks whether the record requires any confirmation.
185
+ def pending_any_confirmation
186
+ if (!confirmed? || pending_reconfirmation?)
187
+ yield
188
+ else
189
+ self.errors.add(:email, :already_confirmed)
190
+ false
191
+ end
192
+ end
193
+
194
+ # Generates a new random token for confirmation, and stores the time
195
+ # this token is being generated
196
+ def generate_confirmation_token
197
+ self.confirmation_token = self.class.confirmation_token
198
+ self.confirmation_sent_at = Time.now.utc
199
+ end
200
+
201
+ def generate_confirmation_token!
202
+ generate_confirmation_token && save(:validate => false)
203
+ end
204
+
205
+ def after_password_reset
206
+ super
207
+ confirm! unless confirmed?
208
+ end
209
+
210
+ def postpone_email_change_until_confirmation
211
+ @reconfirmation_required = true
212
+ self.unconfirmed_email = self.email
213
+ self.email = self.email_was
214
+ end
215
+
216
+ def postpone_email_change?
217
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone
218
+ @bypass_postpone = nil
219
+ postpone
220
+ end
221
+
222
+ def reconfirmation_required?
223
+ self.class.reconfirmable && @reconfirmation_required
224
+ end
225
+
226
+ module ClassMethods
227
+ # Attempt to find a user by its email. If a record is found, send new
228
+ # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
229
+ # field. If no user is found, returns a new user with an email not found error.
230
+ # Options must contain the user email
231
+ def send_confirmation_instructions(attributes={})
232
+ confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
233
+ unless confirmable.try(:persisted?)
234
+ confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
235
+ end
236
+ confirmable.resend_confirmation_token if confirmable.persisted?
237
+ confirmable
238
+ end
239
+
240
+ # Find a user by its confirmation token and try to confirm it.
241
+ # If no user is found, returns a new user with an error.
242
+ # If the user is already confirmed, create an error for the user
243
+ # Options must have the confirmation_token
244
+ def confirm_by_token(confirmation_token)
245
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
246
+ confirmable.confirm! if confirmable.persisted?
247
+ confirmable
248
+ end
249
+
250
+ # Generate a token checking if one does not already exist in the database.
251
+ def confirmation_token
252
+ generate_token(:confirmation_token)
253
+ end
254
+
255
+ # Find a record for confirmation by unconfirmed email field
256
+ def find_by_unconfirmed_email_with_errors(attributes = {})
257
+ unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
258
+ unconfirmed_attributes = attributes.symbolize_keys
259
+ unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
260
+ find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
261
+ end
262
+
263
+ Devise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,126 @@
1
+ require 'devise/strategies/database_authenticatable'
2
+ require 'bcrypt'
3
+
4
+ module Devise
5
+ module Models
6
+ # Authenticatable Module, responsible for encrypting password and validating
7
+ # authenticity of a user while signing in.
8
+ #
9
+ # == Options
10
+ #
11
+ # DatabaseAuthenticable adds the following options to devise_for:
12
+ #
13
+ # * +pepper+: a random string used to provide a more secure hash. Use
14
+ # `rake secret` to generate new keys.
15
+ #
16
+ # * +stretches+: the cost given to bcrypt.
17
+ #
18
+ # == Examples
19
+ #
20
+ # User.find(1).valid_password?('password123') # returns true/false
21
+ #
22
+ module DatabaseAuthenticatable
23
+ extend ActiveSupport::Concern
24
+
25
+ included do
26
+ attr_reader :password, :current_password
27
+ attr_accessor :password_confirmation
28
+ end
29
+
30
+ def self.required_fields(klass)
31
+ [:encrypted_password] + klass.authentication_keys
32
+ end
33
+
34
+ # Generates password encryption based on the given value.
35
+ def password=(new_password)
36
+ @password = new_password
37
+ self.encrypted_password = password_digest(@password) if @password.present?
38
+ end
39
+
40
+ # Verifies whether an password (ie from sign in) is the user password.
41
+ def valid_password?(password)
42
+ return false if encrypted_password.blank?
43
+ bcrypt = ::BCrypt::Password.new(encrypted_password)
44
+ password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
45
+ Devise.secure_compare(password, encrypted_password)
46
+ end
47
+
48
+ # Set password and password confirmation to nil
49
+ def clean_up_passwords
50
+ self.password = self.password_confirmation = nil
51
+ end
52
+
53
+ # Update record attributes when :current_password matches, otherwise returns
54
+ # error on :current_password. It also automatically rejects :password and
55
+ # :password_confirmation if they are blank.
56
+ def update_with_password(params, *options)
57
+ current_password = params.delete(:current_password)
58
+
59
+ if params[:password].blank?
60
+ params.delete(:password)
61
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
62
+ end
63
+
64
+ result = if valid_password?(current_password)
65
+ update_attributes(params, *options)
66
+ else
67
+ self.assign_attributes(params, *options)
68
+ self.valid?
69
+ self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
70
+ false
71
+ end
72
+
73
+ clean_up_passwords
74
+ result
75
+ end
76
+
77
+ # Updates record attributes without asking for the current password.
78
+ # Never allows a change to the current password. If you are using this
79
+ # method, you should probably override this method to protect other
80
+ # attributes you would not like to be updated without a password.
81
+ #
82
+ # Example:
83
+ #
84
+ # def update_without_password(params={})
85
+ # params.delete(:email)
86
+ # super(params)
87
+ # end
88
+ #
89
+ def update_without_password(params, *options)
90
+ params.delete(:password)
91
+ params.delete(:password_confirmation)
92
+
93
+ result = update_attributes(params, *options)
94
+ clean_up_passwords
95
+ result
96
+ end
97
+
98
+ def after_database_authentication
99
+ end
100
+
101
+ # A reliable way to expose the salt regardless of the implementation.
102
+ def authenticatable_salt
103
+ encrypted_password[0,29] if encrypted_password
104
+ end
105
+
106
+ protected
107
+
108
+ # Digests the password using bcrypt.
109
+ def password_digest(password)
110
+ ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
111
+ end
112
+
113
+ module ClassMethods
114
+ Devise::Models.config(self, :pepper, :stretches)
115
+
116
+ # We assume this method already gets the sanitized values from the
117
+ # DatabaseAuthenticatable strategy. If you are using this method on
118
+ # your own, be sure to sanitize the conditions hash to only include
119
+ # the proper fields.
120
+ def find_for_database_authentication(conditions)
121
+ find_for_authentication(conditions)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,193 @@
1
+ require "devise/hooks/lockable"
2
+
3
+ module Devise
4
+ module Models
5
+ # Handles blocking a user access after a certain number of attempts.
6
+ # Lockable accepts two different strategies to unlock a user after it's
7
+ # blocked: email and time. The former will send an email to the user when
8
+ # the lock happens, containing a link to unlock its account. The second
9
+ # will unlock the user automatically after some configured time (ie 2.hours).
10
+ # It's also possible to setup lockable to use both email and time strategies.
11
+ #
12
+ # == Options
13
+ #
14
+ # Lockable adds the following options to +devise+:
15
+ #
16
+ # * +maximum_attempts+: how many attempts should be accepted before blocking the user.
17
+ # * +lock_strategy+: lock the user account by :failed_attempts or :none.
18
+ # * +unlock_strategy+: unlock the user account by :time, :email, :both or :none.
19
+ # * +unlock_in+: the time you want to lock the user after to lock happens. Only available when unlock_strategy is :time or :both.
20
+ # * +unlock_keys+: the keys you want to use when locking and unlocking an account
21
+ #
22
+ module Lockable
23
+ extend ActiveSupport::Concern
24
+
25
+ delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
26
+
27
+ def self.required_fields(klass)
28
+ attributes = []
29
+ attributes << :failed_attempts if klass.lock_strategy_enabled?(:failed_attempts)
30
+ attributes << :locked_at if klass.unlock_strategy_enabled?(:time)
31
+ attributes << :unlock_token if klass.unlock_strategy_enabled?(:email)
32
+
33
+ attributes
34
+ end
35
+
36
+ # Lock a user setting its locked_at to actual time.
37
+ def lock_access!
38
+ self.locked_at = Time.now.utc
39
+
40
+ if unlock_strategy_enabled?(:email)
41
+ generate_unlock_token!
42
+ send_unlock_instructions
43
+ else
44
+ save(:validate => false)
45
+ end
46
+ end
47
+
48
+ # Unlock a user by cleaning locked_at and failed_attempts.
49
+ def unlock_access!
50
+ self.locked_at = nil
51
+ self.failed_attempts = 0 if respond_to?(:failed_attempts=)
52
+ self.unlock_token = nil if respond_to?(:unlock_token=)
53
+ save(:validate => false)
54
+ end
55
+
56
+ # Verifies whether a user is locked or not.
57
+ def access_locked?
58
+ locked_at && !lock_expired?
59
+ end
60
+
61
+ # Send unlock instructions by email
62
+ def send_unlock_instructions
63
+ send_devise_notification(:unlock_instructions)
64
+ end
65
+
66
+ # Resend the unlock instructions if the user is locked.
67
+ def resend_unlock_token
68
+ if_access_locked { send_unlock_instructions }
69
+ end
70
+
71
+ # Overwrites active_for_authentication? from Devise::Models::Activatable for locking purposes
72
+ # by verifying whether a user is active to sign in or not based on locked?
73
+ def active_for_authentication?
74
+ super && !access_locked?
75
+ end
76
+
77
+ # Overwrites invalid_message from Devise::Models::Authenticatable to define
78
+ # the correct reason for blocking the sign in.
79
+ def inactive_message
80
+ access_locked? ? :locked : super
81
+ end
82
+
83
+ # Overwrites valid_for_authentication? from Devise::Models::Authenticatable
84
+ # for verifying whether a user is allowed to sign in or not. If the user
85
+ # is locked, it should never be allowed.
86
+ def valid_for_authentication?
87
+ return super unless persisted? && lock_strategy_enabled?(:failed_attempts)
88
+
89
+ # Unlock the user if the lock is expired, no matter
90
+ # if the user can login or not (wrong password, etc)
91
+ unlock_access! if lock_expired?
92
+
93
+ if super && !access_locked?
94
+ true
95
+ else
96
+ self.failed_attempts ||= 0
97
+ self.failed_attempts += 1
98
+ if attempts_exceeded?
99
+ lock_access! unless access_locked?
100
+ else
101
+ save(:validate => false)
102
+ end
103
+ false
104
+ end
105
+ end
106
+
107
+ def unauthenticated_message
108
+ # If set to paranoid mode, do not show the locked message because it
109
+ # leaks the existence of an account.
110
+ if Devise.paranoid
111
+ super
112
+ elsif lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
113
+ :locked
114
+ else
115
+ super
116
+ end
117
+ end
118
+
119
+ protected
120
+
121
+ def attempts_exceeded?
122
+ self.failed_attempts > self.class.maximum_attempts
123
+ end
124
+
125
+ # Generates unlock token
126
+ def generate_unlock_token
127
+ self.unlock_token = self.class.unlock_token
128
+ end
129
+
130
+ def generate_unlock_token!
131
+ generate_unlock_token && save(:validate => false)
132
+ end
133
+
134
+ # Tells if the lock is expired if :time unlock strategy is active
135
+ def lock_expired?
136
+ if unlock_strategy_enabled?(:time)
137
+ locked_at && locked_at < self.class.unlock_in.ago
138
+ else
139
+ false
140
+ end
141
+ end
142
+
143
+ # Checks whether the record is locked or not, yielding to the block
144
+ # if it's locked, otherwise adds an error to email.
145
+ def if_access_locked
146
+ if access_locked?
147
+ yield
148
+ else
149
+ self.errors.add(:email, :not_locked)
150
+ false
151
+ end
152
+ end
153
+
154
+ module ClassMethods
155
+ # Attempt to find a user by its email. If a record is found, send new
156
+ # unlock instructions to it. If not user is found, returns a new user
157
+ # with an email not found error.
158
+ # Options must contain the user email
159
+ def send_unlock_instructions(attributes={})
160
+ lockable = find_or_initialize_with_errors(unlock_keys, attributes, :not_found)
161
+ lockable.resend_unlock_token if lockable.persisted?
162
+ lockable
163
+ end
164
+
165
+ # Find a user by its unlock token and try to unlock it.
166
+ # If no user is found, returns a new user with an error.
167
+ # If the user is not locked, creates an error for the user
168
+ # Options must have the unlock_token
169
+ def unlock_access_by_token(unlock_token)
170
+ lockable = find_or_initialize_with_error_by(:unlock_token, unlock_token)
171
+ lockable.unlock_access! if lockable.persisted?
172
+ lockable
173
+ end
174
+
175
+ # Is the unlock enabled for the given unlock strategy?
176
+ def unlock_strategy_enabled?(strategy)
177
+ [:both, strategy].include?(self.unlock_strategy)
178
+ end
179
+
180
+ # Is the lock enabled for the given lock strategy?
181
+ def lock_strategy_enabled?(strategy)
182
+ self.lock_strategy == strategy
183
+ end
184
+
185
+ def unlock_token
186
+ Devise.friendly_token
187
+ end
188
+
189
+ Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
190
+ end
191
+ end
192
+ end
193
+ end