mongoid-devise 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +333 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +260 -0
- data/Rakefile +53 -0
- data/TODO +2 -0
- data/app/controllers/confirmations_controller.rb +33 -0
- data/app/controllers/passwords_controller.rb +42 -0
- data/app/controllers/registrations_controller.rb +55 -0
- data/app/controllers/sessions_controller.rb +45 -0
- data/app/controllers/unlocks_controller.rb +33 -0
- data/app/models/devise_mailer.rb +68 -0
- data/app/views/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/passwords/edit.html.erb +16 -0
- data/app/views/passwords/new.html.erb +12 -0
- data/app/views/registrations/edit.html.erb +25 -0
- data/app/views/registrations/new.html.erb +17 -0
- data/app/views/sessions/new.html.erb +17 -0
- data/app/views/shared/_devise_links.erb +19 -0
- data/app/views/unlocks/new.html.erb +12 -0
- data/generators/devise/USAGE +5 -0
- data/generators/devise/devise_generator.rb +15 -0
- data/generators/devise/lib/route_devise.rb +32 -0
- data/generators/devise/templates/migration.rb +23 -0
- data/generators/devise/templates/model.rb +9 -0
- data/generators/devise_install/USAGE +3 -0
- data/generators/devise_install/devise_install_generator.rb +15 -0
- data/generators/devise_install/templates/README +18 -0
- data/generators/devise_install/templates/devise.rb +102 -0
- data/generators/devise_views/USAGE +3 -0
- data/generators/devise_views/devise_views_generator.rb +21 -0
- data/init.rb +2 -0
- data/lib/devise.rb +253 -0
- data/lib/devise/controllers/helpers.rb +200 -0
- data/lib/devise/controllers/internal_helpers.rb +129 -0
- data/lib/devise/controllers/url_helpers.rb +41 -0
- data/lib/devise/encryptors/authlogic_sha512.rb +21 -0
- data/lib/devise/encryptors/base.rb +20 -0
- data/lib/devise/encryptors/bcrypt.rb +21 -0
- data/lib/devise/encryptors/clearance_sha1.rb +19 -0
- data/lib/devise/encryptors/restful_authentication_sha1.rb +22 -0
- data/lib/devise/encryptors/sha1.rb +27 -0
- data/lib/devise/encryptors/sha512.rb +27 -0
- data/lib/devise/failure_app.rb +65 -0
- data/lib/devise/hooks/activatable.rb +15 -0
- data/lib/devise/hooks/rememberable.rb +30 -0
- data/lib/devise/hooks/timeoutable.rb +18 -0
- data/lib/devise/hooks/trackable.rb +18 -0
- data/lib/devise/locales/en.yml +35 -0
- data/lib/devise/mapping.rb +131 -0
- data/lib/devise/models.rb +112 -0
- data/lib/devise/models/activatable.rb +16 -0
- data/lib/devise/models/authenticatable.rb +146 -0
- data/lib/devise/models/confirmable.rb +172 -0
- data/lib/devise/models/http_authenticatable.rb +21 -0
- data/lib/devise/models/lockable.rb +160 -0
- data/lib/devise/models/recoverable.rb +80 -0
- data/lib/devise/models/registerable.rb +8 -0
- data/lib/devise/models/rememberable.rb +94 -0
- data/lib/devise/models/timeoutable.rb +28 -0
- data/lib/devise/models/token_authenticatable.rb +89 -0
- data/lib/devise/models/trackable.rb +16 -0
- data/lib/devise/models/validatable.rb +48 -0
- data/lib/devise/orm/active_record.rb +41 -0
- data/lib/devise/orm/data_mapper.rb +83 -0
- data/lib/devise/orm/mongo_mapper.rb +51 -0
- data/lib/devise/orm/mongoid.rb +60 -0
- data/lib/devise/rails.rb +14 -0
- data/lib/devise/rails/routes.rb +125 -0
- data/lib/devise/rails/warden_compat.rb +25 -0
- data/lib/devise/schema.rb +65 -0
- data/lib/devise/strategies/authenticatable.rb +36 -0
- data/lib/devise/strategies/base.rb +16 -0
- data/lib/devise/strategies/http_authenticatable.rb +49 -0
- data/lib/devise/strategies/rememberable.rb +37 -0
- data/lib/devise/strategies/token_authenticatable.rb +37 -0
- data/lib/devise/test_helpers.rb +86 -0
- data/lib/devise/version.rb +3 -0
- data/test/controllers/helpers_test.rb +177 -0
- data/test/controllers/internal_helpers_test.rb +55 -0
- data/test/controllers/url_helpers_test.rb +47 -0
- data/test/devise_test.rb +69 -0
- data/test/encryptors_test.rb +31 -0
- data/test/failure_app_test.rb +44 -0
- data/test/integration/authenticatable_test.rb +271 -0
- data/test/integration/confirmable_test.rb +97 -0
- data/test/integration/http_authenticatable_test.rb +44 -0
- data/test/integration/lockable_test.rb +83 -0
- data/test/integration/recoverable_test.rb +141 -0
- data/test/integration/registerable_test.rb +130 -0
- data/test/integration/rememberable_test.rb +63 -0
- data/test/integration/timeoutable_test.rb +68 -0
- data/test/integration/token_authenticatable_test.rb +55 -0
- data/test/integration/trackable_test.rb +64 -0
- data/test/mailers/confirmation_instructions_test.rb +80 -0
- data/test/mailers/reset_password_instructions_test.rb +68 -0
- data/test/mailers/unlock_instructions_test.rb +62 -0
- data/test/mapping_test.rb +153 -0
- data/test/models/authenticatable_test.rb +180 -0
- data/test/models/confirmable_test.rb +228 -0
- data/test/models/lockable_test.rb +202 -0
- data/test/models/recoverable_test.rb +138 -0
- data/test/models/rememberable_test.rb +135 -0
- data/test/models/timeoutable_test.rb +28 -0
- data/test/models/token_authenticatable_test.rb +51 -0
- data/test/models/trackable_test.rb +5 -0
- data/test/models/validatable_test.rb +106 -0
- data/test/models_test.rb +56 -0
- data/test/orm/active_record.rb +31 -0
- data/test/orm/mongo_mapper.rb +20 -0
- data/test/orm/mongoid.rb +22 -0
- data/test/rails_app/app/active_record/admin.rb +7 -0
- data/test/rails_app/app/active_record/user.rb +7 -0
- data/test/rails_app/app/controllers/admins_controller.rb +6 -0
- data/test/rails_app/app/controllers/application_controller.rb +10 -0
- data/test/rails_app/app/controllers/home_controller.rb +4 -0
- data/test/rails_app/app/controllers/users_controller.rb +16 -0
- data/test/rails_app/app/helpers/application_helper.rb +3 -0
- data/test/rails_app/app/mongo_mapper/admin.rb +9 -0
- data/test/rails_app/app/mongo_mapper/user.rb +8 -0
- data/test/rails_app/app/mongoid/admin.rb +9 -0
- data/test/rails_app/app/mongoid/user.rb +8 -0
- data/test/rails_app/config/boot.rb +110 -0
- data/test/rails_app/config/environment.rb +42 -0
- data/test/rails_app/config/environments/development.rb +17 -0
- data/test/rails_app/config/environments/production.rb +28 -0
- data/test/rails_app/config/environments/test.rb +28 -0
- data/test/rails_app/config/initializers/devise.rb +79 -0
- data/test/rails_app/config/initializers/inflections.rb +2 -0
- data/test/rails_app/config/initializers/new_rails_defaults.rb +24 -0
- data/test/rails_app/config/initializers/session_store.rb +15 -0
- data/test/rails_app/config/routes.rb +21 -0
- data/test/routes_test.rb +110 -0
- data/test/support/assertions_helper.rb +37 -0
- data/test/support/integration_tests_helper.rb +71 -0
- data/test/support/test_silencer.rb +5 -0
- data/test/support/tests_helper.rb +39 -0
- data/test/test_helper.rb +21 -0
- data/test/test_helpers_test.rb +57 -0
- metadata +216 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'devise/hooks/activatable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
# This module implements the default API required in activatable hook.
|
|
6
|
+
module Activatable
|
|
7
|
+
def active?
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def inactive_message
|
|
12
|
+
:inactive
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
require 'devise/strategies/authenticatable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
# Authenticable Module, responsible for encrypting password and validating
|
|
6
|
+
# authenticity of a user while signing in.
|
|
7
|
+
#
|
|
8
|
+
# Configuration:
|
|
9
|
+
#
|
|
10
|
+
# You can overwrite configuration values by setting in globally in Devise,
|
|
11
|
+
# using devise method or overwriting the respective instance method.
|
|
12
|
+
#
|
|
13
|
+
# pepper: encryption key used for creating encrypted password. Each time
|
|
14
|
+
# password changes, it's gonna be encrypted again, and this key
|
|
15
|
+
# is added to the password and salt to create a secure hash.
|
|
16
|
+
# Always use `rake secret' to generate a new key.
|
|
17
|
+
#
|
|
18
|
+
# stretches: defines how many times the password will be encrypted.
|
|
19
|
+
#
|
|
20
|
+
# encryptor: the encryptor going to be used. By default :sha1.
|
|
21
|
+
#
|
|
22
|
+
# authentication_keys: parameters used for authentication. By default [:email]
|
|
23
|
+
#
|
|
24
|
+
# Examples:
|
|
25
|
+
#
|
|
26
|
+
# User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
|
|
27
|
+
# User.find(1).valid_password?('password123') # returns true/false
|
|
28
|
+
#
|
|
29
|
+
module Authenticatable
|
|
30
|
+
def self.included(base)
|
|
31
|
+
base.class_eval do
|
|
32
|
+
extend ClassMethods
|
|
33
|
+
|
|
34
|
+
attr_reader :password, :current_password
|
|
35
|
+
attr_accessor :password_confirmation
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# TODO Remove me in next release
|
|
40
|
+
def old_password
|
|
41
|
+
ActiveSupport::Deprecation.warn "old_password is deprecated, please use current_password instead", caller
|
|
42
|
+
@old_password
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Regenerates password salt and encrypted password each time password is set,
|
|
46
|
+
# and then trigger any "after_changed_password"-callbacks.
|
|
47
|
+
def password=(new_password)
|
|
48
|
+
@password = new_password
|
|
49
|
+
|
|
50
|
+
if @password.present?
|
|
51
|
+
self.password_salt = self.class.encryptor_class.salt
|
|
52
|
+
self.encrypted_password = password_digest(@password)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Verifies whether an incoming_password (ie from sign in) is the user password.
|
|
57
|
+
def valid_password?(incoming_password)
|
|
58
|
+
password_digest(incoming_password) == self.encrypted_password
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
|
|
62
|
+
# is the user authentication token.
|
|
63
|
+
def valid_authentication_token?(incoming_auth_token)
|
|
64
|
+
incoming_auth_token == self.authentication_token
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Checks if a resource is valid upon authentication.
|
|
68
|
+
def valid_for_authentication?(attributes)
|
|
69
|
+
valid_password?(attributes[:password])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Set password and password confirmation to nil
|
|
73
|
+
def clean_up_passwords
|
|
74
|
+
self.password = self.password_confirmation = nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Update record attributes when :current_password matches, otherwise returns
|
|
78
|
+
# error on :current_password. It also automatically rejects :password and
|
|
79
|
+
# :password_confirmation if they are blank.
|
|
80
|
+
def update_with_password(params={})
|
|
81
|
+
# TODO Remove me in next release
|
|
82
|
+
if params[:old_password].present?
|
|
83
|
+
params[:current_password] ||= params[:old_password]
|
|
84
|
+
ActiveSupport::Deprecation.warn "old_password is deprecated, please use current_password instead", caller
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
params.delete(:password) if params[:password].blank?
|
|
88
|
+
params.delete(:password_confirmation) if params[:password_confirmation].blank?
|
|
89
|
+
current_password = params.delete(:current_password)
|
|
90
|
+
|
|
91
|
+
result = if valid_password?(current_password)
|
|
92
|
+
update_attributes(params)
|
|
93
|
+
else
|
|
94
|
+
message = current_password.blank? ? :blank : :invalid
|
|
95
|
+
self.class.add_error_on(self, :current_password, message, false)
|
|
96
|
+
self.attributes = params
|
|
97
|
+
false
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
clean_up_passwords unless result
|
|
101
|
+
result
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
protected
|
|
105
|
+
|
|
106
|
+
# Digests the password using the configured encryptor.
|
|
107
|
+
def password_digest(password)
|
|
108
|
+
self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
module ClassMethods
|
|
112
|
+
Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys)
|
|
113
|
+
|
|
114
|
+
# Authenticate a user based on configured attribute keys. Returns the
|
|
115
|
+
# authenticated user if it's valid or nil.
|
|
116
|
+
def authenticate(attributes={})
|
|
117
|
+
return unless authentication_keys.all? { |k| attributes[k].present? }
|
|
118
|
+
conditions = attributes.slice(*authentication_keys)
|
|
119
|
+
resource = find_for_authentication(conditions)
|
|
120
|
+
resource if resource.try(:valid_for_authentication?, attributes)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns the class for the configured encryptor.
|
|
124
|
+
def encryptor_class
|
|
125
|
+
@encryptor_class ||= ::Devise::Encryptors.const_get(encryptor.to_s.classify)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
protected
|
|
129
|
+
|
|
130
|
+
# Find first record based on conditions given (ie by the sign in form).
|
|
131
|
+
# Overwrite to add customized conditions, create a join, or maybe use a
|
|
132
|
+
# namedscope to filter records while authenticating.
|
|
133
|
+
# Example:
|
|
134
|
+
#
|
|
135
|
+
# def self.find_for_authentication(conditions={})
|
|
136
|
+
# conditions[:active] = true
|
|
137
|
+
# find(:first, :conditions => conditions)
|
|
138
|
+
# end
|
|
139
|
+
#
|
|
140
|
+
def find_for_authentication(conditions)
|
|
141
|
+
find(:first, :conditions => conditions)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
require 'devise/models/activatable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
|
|
6
|
+
# Confirmable is responsible to verify if an account is already confirmed to
|
|
7
|
+
# sign in, and to send emails with confirmation instructions.
|
|
8
|
+
# Confirmation instructions are sent to the user email after creating a
|
|
9
|
+
# record, after updating it's email and also when manually requested by
|
|
10
|
+
# a new confirmation instruction request.
|
|
11
|
+
# Whenever the user update it's email, his account is automatically unconfirmed,
|
|
12
|
+
# it means it won't be able to sign in again without confirming the account
|
|
13
|
+
# again through the email that was sent.
|
|
14
|
+
#
|
|
15
|
+
# Configuration:
|
|
16
|
+
#
|
|
17
|
+
# confirm_within: the time you want the user will have to confirm it's account
|
|
18
|
+
# without blocking his access. When confirm_within is zero, the
|
|
19
|
+
# user won't be able to sign in without confirming. You can
|
|
20
|
+
# use this to let your user access some features of your
|
|
21
|
+
# application without confirming the account, but blocking it
|
|
22
|
+
# after a certain period (ie 7 days). By default confirm_within is
|
|
23
|
+
# zero, it means users always have to confirm to sign in.
|
|
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
|
+
# User.find(1).resend_confirmation! # generates a new token and resent it
|
|
31
|
+
module Confirmable
|
|
32
|
+
include Devise::Models::Activatable
|
|
33
|
+
|
|
34
|
+
def self.included(base)
|
|
35
|
+
base.class_eval do
|
|
36
|
+
extend ClassMethods
|
|
37
|
+
|
|
38
|
+
before_create :generate_confirmation_token, :if => :confirmation_required?
|
|
39
|
+
after_create :send_confirmation_instructions, :if => :confirmation_required?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Confirm a user by setting it's confirmed_at to actual time. If the user
|
|
44
|
+
# is already confirmed, add en error to email field
|
|
45
|
+
def confirm!
|
|
46
|
+
unless_confirmed do
|
|
47
|
+
self.confirmation_token = nil
|
|
48
|
+
self.confirmed_at = Time.now
|
|
49
|
+
save(false)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Verifies whether a user is confirmed or not
|
|
54
|
+
def confirmed?
|
|
55
|
+
!new_record? && !confirmed_at.nil?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Send confirmation instructions by email
|
|
59
|
+
def send_confirmation_instructions
|
|
60
|
+
::DeviseMailer.deliver_confirmation_instructions(self)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Remove confirmation date and send confirmation instructions, to ensure
|
|
64
|
+
# after sending these instructions the user won't be able to sign in without
|
|
65
|
+
# confirming it's account
|
|
66
|
+
def resend_confirmation!
|
|
67
|
+
unless_confirmed do
|
|
68
|
+
generate_confirmation_token
|
|
69
|
+
save(false)
|
|
70
|
+
send_confirmation_instructions
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Overwrites active? from Devise::Models::Activatable for confirmation
|
|
75
|
+
# by verifying whether an user is active to sign in or not. If the user
|
|
76
|
+
# is already confirmed, it should never be blocked. Otherwise we need to
|
|
77
|
+
# calculate if the confirm time has not expired for this user.
|
|
78
|
+
def active?
|
|
79
|
+
super && (confirmed? || confirmation_period_valid?)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The message to be shown if the account is inactive.
|
|
83
|
+
def inactive_message
|
|
84
|
+
if !confirmed?
|
|
85
|
+
:unconfirmed
|
|
86
|
+
else
|
|
87
|
+
super
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# If you don't want confirmation to be sent on create, neither a code
|
|
92
|
+
# to be generated, call skip_confirmation!
|
|
93
|
+
def skip_confirmation!
|
|
94
|
+
self.confirmed_at = Time.now
|
|
95
|
+
@skip_confirmation = true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
# Callback to overwrite if confirmation is required or not.
|
|
101
|
+
def confirmation_required?
|
|
102
|
+
!@skip_confirmation
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Checks if the confirmation for the user is within the limit time.
|
|
106
|
+
# We do this by calculating if the difference between today and the
|
|
107
|
+
# confirmation sent date does not exceed the confirm in time configured.
|
|
108
|
+
# Confirm_in is a model configuration, must always be an integer value.
|
|
109
|
+
#
|
|
110
|
+
# Example:
|
|
111
|
+
#
|
|
112
|
+
# # confirm_within = 1.day and confirmation_sent_at = today
|
|
113
|
+
# confirmation_period_valid? # returns true
|
|
114
|
+
#
|
|
115
|
+
# # confirm_within = 5.days and confirmation_sent_at = 4.days.ago
|
|
116
|
+
# confirmation_period_valid? # returns true
|
|
117
|
+
#
|
|
118
|
+
# # confirm_within = 5.days and confirmation_sent_at = 5.days.ago
|
|
119
|
+
# confirmation_period_valid? # returns false
|
|
120
|
+
#
|
|
121
|
+
# # confirm_within = 0.days
|
|
122
|
+
# confirmation_period_valid? # will always return false
|
|
123
|
+
#
|
|
124
|
+
def confirmation_period_valid?
|
|
125
|
+
confirmation_sent_at && confirmation_sent_at.utc >= self.class.confirm_within.ago
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Checks whether the record is confirmed or not, yielding to the block
|
|
129
|
+
# if it's already confirmed, otherwise adds an error to email.
|
|
130
|
+
def unless_confirmed
|
|
131
|
+
unless confirmed?
|
|
132
|
+
yield
|
|
133
|
+
else
|
|
134
|
+
self.class.add_error_on(self, :email, :already_confirmed)
|
|
135
|
+
false
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Generates a new random token for confirmation, and stores the time
|
|
140
|
+
# this token is being generated
|
|
141
|
+
def generate_confirmation_token
|
|
142
|
+
self.confirmed_at = nil
|
|
143
|
+
self.confirmation_token = Devise.friendly_token
|
|
144
|
+
self.confirmation_sent_at = Time.now.utc
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
module ClassMethods
|
|
148
|
+
# Attempt to find a user by it's email. If a record is found, send new
|
|
149
|
+
# confirmation instructions to it. If not user is found, returns a new user
|
|
150
|
+
# with an email not found error.
|
|
151
|
+
# Options must contain the user email
|
|
152
|
+
def send_confirmation_instructions(attributes={})
|
|
153
|
+
confirmable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
|
|
154
|
+
confirmable.resend_confirmation! unless confirmable.new_record?
|
|
155
|
+
confirmable
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Find a user by it's confirmation token and try to confirm it.
|
|
159
|
+
# If no user is found, returns a new user with an error.
|
|
160
|
+
# If the user is already confirmed, create an error for the user
|
|
161
|
+
# Options must have the confirmation_token
|
|
162
|
+
def confirm!(attributes={})
|
|
163
|
+
confirmable = find_or_initialize_with_error_by(:confirmation_token, attributes[:confirmation_token])
|
|
164
|
+
confirmable.confirm! unless confirmable.new_record?
|
|
165
|
+
confirmable
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
Devise::Models.config(self, :confirm_within)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'devise/strategies/http_authenticatable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
# Adds HttpAuthenticatable behavior to your model. It expects that your
|
|
6
|
+
# model class responds to authenticate and authentication_keys methods
|
|
7
|
+
# (which for example are defined in authenticatable).
|
|
8
|
+
module HttpAuthenticatable
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.extend ClassMethods
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
# Authenticate an user using http.
|
|
15
|
+
def authenticate_with_http(username, password)
|
|
16
|
+
authenticate(authentication_keys.first => username, :password => password)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
require 'devise/models/activatable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
|
|
6
|
+
# Handles blocking a user access after a certain number of attempts.
|
|
7
|
+
# Lockable accepts two different strategies to unlock a user after it's
|
|
8
|
+
# blocked: email and time. The former will send an email to the user when
|
|
9
|
+
# the lock happens, containing a link to unlock it's account. The second
|
|
10
|
+
# will unlock the user automatically after some configured time (ie 2.hours).
|
|
11
|
+
# It's also possible to setup lockable to use both email and time strategies.
|
|
12
|
+
#
|
|
13
|
+
# Configuration:
|
|
14
|
+
#
|
|
15
|
+
# maximum_attempts: how many attempts should be accepted before blocking the user.
|
|
16
|
+
# unlock_strategy: unlock the user account by :time, :email or :both.
|
|
17
|
+
# unlock_in: the time you want to lock the user after to lock happens. Only
|
|
18
|
+
# available when unlock_strategy is :time or :both.
|
|
19
|
+
#
|
|
20
|
+
module Lockable
|
|
21
|
+
include Devise::Models::Activatable
|
|
22
|
+
|
|
23
|
+
def self.included(base)
|
|
24
|
+
base.class_eval do
|
|
25
|
+
extend ClassMethods
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Lock an user setting it's locked_at to actual time.
|
|
30
|
+
def lock
|
|
31
|
+
self.locked_at = Time.now
|
|
32
|
+
if unlock_strategy_enabled?(:email)
|
|
33
|
+
generate_unlock_token
|
|
34
|
+
send_unlock_instructions
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Lock an user also saving the record.
|
|
39
|
+
def lock!
|
|
40
|
+
lock
|
|
41
|
+
save(false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Unlock an user by cleaning locket_at and failed_attempts.
|
|
45
|
+
def unlock!
|
|
46
|
+
if_locked do
|
|
47
|
+
self.locked_at = nil
|
|
48
|
+
self.failed_attempts = 0
|
|
49
|
+
self.unlock_token = nil
|
|
50
|
+
save(false)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Verifies whether a user is locked or not.
|
|
55
|
+
def locked?
|
|
56
|
+
locked_at && !lock_expired?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Send unlock instructions by email
|
|
60
|
+
def send_unlock_instructions
|
|
61
|
+
::DeviseMailer.deliver_unlock_instructions(self)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Resend the unlock instructions if the user is locked.
|
|
65
|
+
def resend_unlock!
|
|
66
|
+
if_locked do
|
|
67
|
+
generate_unlock_token unless unlock_token.present?
|
|
68
|
+
save(false)
|
|
69
|
+
send_unlock_instructions
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Overwrites active? from Devise::Models::Activatable for locking purposes
|
|
74
|
+
# by verifying whether an user is active to sign in or not based on locked?
|
|
75
|
+
def active?
|
|
76
|
+
super && !locked?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Overwrites invalid_message from Devise::Models::Authenticatable to define
|
|
80
|
+
# the correct reason for blocking the sign in.
|
|
81
|
+
def inactive_message
|
|
82
|
+
if locked?
|
|
83
|
+
:locked
|
|
84
|
+
else
|
|
85
|
+
super
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Overwrites valid_for_authentication? from Devise::Models::Authenticatable
|
|
90
|
+
# for verifying whether an user is allowed to sign in or not. If the user
|
|
91
|
+
# is locked, it should never be allowed.
|
|
92
|
+
def valid_for_authentication?(attributes)
|
|
93
|
+
if result = super
|
|
94
|
+
self.failed_attempts = 0
|
|
95
|
+
else
|
|
96
|
+
self.failed_attempts += 1
|
|
97
|
+
lock if failed_attempts > self.class.maximum_attempts
|
|
98
|
+
end
|
|
99
|
+
save(false)
|
|
100
|
+
result
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
protected
|
|
104
|
+
|
|
105
|
+
# Generates unlock token
|
|
106
|
+
def generate_unlock_token
|
|
107
|
+
self.unlock_token = Devise.friendly_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_locked
|
|
122
|
+
if locked?
|
|
123
|
+
yield
|
|
124
|
+
else
|
|
125
|
+
self.class.add_error_on(self, :email, :not_locked)
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Is the unlock enabled for the given unlock strategy?
|
|
131
|
+
def unlock_strategy_enabled?(strategy)
|
|
132
|
+
[:both, strategy].include?(self.class.unlock_strategy)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
module ClassMethods
|
|
136
|
+
# Attempt to find a user by it's email. If a record is found, send new
|
|
137
|
+
# unlock instructions to it. If not user is found, returns a new user
|
|
138
|
+
# with an email not found error.
|
|
139
|
+
# Options must contain the user email
|
|
140
|
+
def send_unlock_instructions(attributes={})
|
|
141
|
+
lockable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
|
|
142
|
+
lockable.resend_unlock! unless lockable.new_record?
|
|
143
|
+
lockable
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Find a user by it's unlock token and try to unlock it.
|
|
147
|
+
# If no user is found, returns a new user with an error.
|
|
148
|
+
# If the user is not locked, creates an error for the user
|
|
149
|
+
# Options must have the unlock_token
|
|
150
|
+
def unlock!(attributes={})
|
|
151
|
+
lockable = find_or_initialize_with_error_by(:unlock_token, attributes[:unlock_token])
|
|
152
|
+
lockable.unlock! unless lockable.new_record?
|
|
153
|
+
lockable
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
Devise::Models.config(self, :maximum_attempts, :unlock_strategy, :unlock_in)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|