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