devise 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.rdoc +94 -0
  4. data/Gemfile +24 -13
  5. data/Gemfile.lock +119 -75
  6. data/MIT-LICENSE +1 -1
  7. data/README.rdoc +144 -101
  8. data/Rakefile +3 -24
  9. data/app/controllers/devise/confirmations_controller.rb +1 -1
  10. data/app/controllers/devise/omniauth_callbacks_controller.rb +26 -0
  11. data/app/controllers/devise/passwords_controller.rb +1 -1
  12. data/app/controllers/devise/registrations_controller.rb +60 -7
  13. data/app/controllers/devise/sessions_controller.rb +6 -4
  14. data/app/controllers/devise/unlocks_controller.rb +1 -1
  15. data/app/helpers/devise_helper.rb +10 -2
  16. data/app/mailers/devise/mailer.rb +27 -10
  17. data/app/views/devise/confirmations/new.html.erb +1 -1
  18. data/app/views/devise/passwords/edit.html.erb +2 -2
  19. data/app/views/devise/passwords/new.html.erb +1 -1
  20. data/app/views/devise/registrations/edit.html.erb +1 -1
  21. data/app/views/devise/registrations/new.html.erb +1 -1
  22. data/app/views/devise/sessions/new.html.erb +1 -1
  23. data/app/views/devise/shared/_links.erb +6 -0
  24. data/app/views/devise/unlocks/new.html.erb +1 -1
  25. data/config/locales/en.yml +11 -2
  26. data/devise.gemspec +25 -0
  27. data/lib/devise/controllers/helpers.rb +119 -116
  28. data/lib/devise/controllers/internal_helpers.rb +29 -8
  29. data/lib/devise/controllers/rememberable.rb +52 -0
  30. data/lib/devise/controllers/scoped_views.rb +4 -6
  31. data/lib/devise/controllers/url_helpers.rb +3 -5
  32. data/lib/devise/encryptors/authlogic_sha512.rb +1 -1
  33. data/lib/devise/encryptors/base.rb +1 -1
  34. data/lib/devise/encryptors/restful_authentication_sha1.rb +4 -4
  35. data/lib/devise/failure_app.rb +46 -10
  36. data/lib/devise/hooks/activatable.rb +2 -2
  37. data/lib/devise/hooks/forgetable.rb +1 -3
  38. data/lib/devise/hooks/rememberable.rb +5 -42
  39. data/lib/devise/hooks/timeoutable.rb +1 -1
  40. data/lib/devise/mapping.rb +18 -7
  41. data/lib/devise/models/authenticatable.rb +73 -31
  42. data/lib/devise/models/confirmable.rb +16 -20
  43. data/lib/devise/models/database_authenticatable.rb +27 -37
  44. data/lib/devise/models/encryptable.rb +72 -0
  45. data/lib/devise/models/lockable.rb +27 -21
  46. data/lib/devise/models/omniauthable.rb +23 -0
  47. data/lib/devise/models/recoverable.rb +13 -3
  48. data/lib/devise/models/registerable.rb +13 -0
  49. data/lib/devise/models/rememberable.rb +39 -34
  50. data/lib/devise/models/timeoutable.rb +20 -3
  51. data/lib/devise/models/token_authenticatable.rb +22 -10
  52. data/lib/devise/models/validatable.rb +16 -4
  53. data/lib/devise/models.rb +4 -16
  54. data/lib/devise/modules.rb +15 -8
  55. data/lib/devise/omniauth/config.rb +18 -0
  56. data/lib/devise/omniauth/url_helpers.rb +33 -0
  57. data/lib/devise/omniauth.rb +32 -0
  58. data/lib/devise/orm/active_record.rb +2 -0
  59. data/lib/devise/orm/mongoid.rb +4 -2
  60. data/lib/devise/rails/routes.rb +67 -20
  61. data/lib/devise/rails/warden_compat.rb +101 -15
  62. data/lib/devise/rails.rb +33 -44
  63. data/lib/devise/schema.rb +16 -16
  64. data/lib/devise/strategies/authenticatable.rb +48 -7
  65. data/lib/devise/strategies/database_authenticatable.rb +1 -1
  66. data/lib/devise/strategies/rememberable.rb +6 -5
  67. data/lib/devise/strategies/token_authenticatable.rb +6 -2
  68. data/lib/devise/test_helpers.rb +13 -3
  69. data/lib/devise/version.rb +1 -1
  70. data/lib/devise.rb +162 -50
  71. data/lib/generators/active_record/devise_generator.rb +2 -2
  72. data/lib/generators/active_record/templates/migration.rb +2 -0
  73. data/lib/generators/devise/devise_generator.rb +3 -1
  74. data/lib/generators/devise/orm_helpers.rb +1 -1
  75. data/lib/generators/devise/views_generator.rb +8 -45
  76. data/lib/generators/mongoid/devise_generator.rb +2 -2
  77. data/lib/generators/templates/devise.rb +82 -39
  78. data/test/controllers/helpers_test.rb +78 -72
  79. data/test/controllers/internal_helpers_test.rb +29 -8
  80. data/test/controllers/url_helpers_test.rb +2 -1
  81. data/test/devise_test.rb +10 -0
  82. data/test/failure_app_test.rb +86 -22
  83. data/test/generators/active_record_generator_test.rb +24 -0
  84. data/test/generators/devise_generator_test.rb +33 -0
  85. data/test/generators/install_generator_test.rb +13 -0
  86. data/test/generators/mongoid_generator_test.rb +22 -0
  87. data/test/generators/views_generator_test.rb +35 -0
  88. data/test/indifferent_hash.rb +33 -0
  89. data/test/integration/authenticatable_test.rb +165 -62
  90. data/test/integration/database_authenticatable_test.rb +22 -0
  91. data/test/integration/http_authenticatable_test.rb +12 -2
  92. data/test/integration/omniauthable_test.rb +138 -0
  93. data/test/integration/recoverable_test.rb +39 -20
  94. data/test/integration/registerable_test.rb +60 -4
  95. data/test/integration/rememberable_test.rb +72 -29
  96. data/test/integration/timeoutable_test.rb +10 -1
  97. data/test/integration/token_authenticatable_test.rb +55 -6
  98. data/test/mailers/confirmation_instructions_test.rb +4 -0
  99. data/test/mailers/reset_password_instructions_test.rb +4 -0
  100. data/test/mailers/unlock_instructions_test.rb +4 -0
  101. data/test/mapping_test.rb +37 -3
  102. data/test/models/confirmable_test.rb +32 -15
  103. data/test/models/database_authenticatable_test.rb +14 -71
  104. data/test/models/encryptable_test.rb +65 -0
  105. data/test/models/lockable_test.rb +48 -11
  106. data/test/models/recoverable_test.rb +28 -2
  107. data/test/models/rememberable_test.rb +186 -125
  108. data/test/models/token_authenticatable_test.rb +19 -1
  109. data/test/models_test.rb +12 -5
  110. data/test/omniauth/url_helpers_test.rb +54 -0
  111. data/test/orm/mongoid.rb +3 -2
  112. data/test/rails_app/Rakefile +10 -0
  113. data/test/rails_app/app/active_record/admin.rb +4 -1
  114. data/test/rails_app/app/active_record/user.rb +5 -4
  115. data/test/rails_app/app/controllers/{sessions_controller.rb → admins/sessions_controller.rb} +1 -1
  116. data/test/rails_app/app/controllers/application_controller.rb +0 -1
  117. data/test/rails_app/app/controllers/home_controller.rb +9 -0
  118. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +14 -0
  119. data/test/rails_app/app/mongoid/admin.rb +4 -1
  120. data/test/rails_app/app/mongoid/shim.rb +16 -3
  121. data/test/rails_app/app/mongoid/user.rb +5 -5
  122. data/test/rails_app/app/views/admins/index.html.erb +1 -0
  123. data/test/rails_app/app/views/admins/sessions/new.html.erb +2 -0
  124. data/test/rails_app/app/views/home/index.html.erb +1 -0
  125. data/test/rails_app/app/views/home/private.html.erb +1 -0
  126. data/test/rails_app/app/views/layouts/application.html.erb +24 -0
  127. data/test/rails_app/app/views/users/index.html.erb +1 -0
  128. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +1 -0
  129. data/test/rails_app/app/views/users/sessions/new.html.erb +1 -0
  130. data/test/rails_app/config/application.rb +5 -0
  131. data/test/rails_app/config/boot.rb +2 -2
  132. data/test/rails_app/config/database.yml +18 -0
  133. data/test/rails_app/config/initializers/devise.rb +71 -31
  134. data/test/rails_app/config/routes.rb +14 -6
  135. data/test/rails_app/config.ru +4 -0
  136. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +21 -17
  137. data/test/rails_app/db/schema.rb +17 -51
  138. data/test/rails_app/lib/shared_admin.rb +9 -0
  139. data/test/rails_app/lib/shared_user.rb +23 -0
  140. data/test/rails_app/public/404.html +26 -0
  141. data/test/rails_app/public/422.html +26 -0
  142. data/test/rails_app/public/500.html +26 -0
  143. data/test/rails_app/public/favicon.ico +0 -0
  144. data/test/rails_app/script/rails +10 -0
  145. data/test/routes_test.rb +42 -9
  146. data/test/schema_test.rb +33 -0
  147. data/test/support/integration.rb +4 -4
  148. data/test/support/locale/en.yml +4 -0
  149. data/test/support/webrat/integrations/rails.rb +11 -19
  150. data/test/test_helper.rb +8 -2
  151. data/test/test_helpers_test.rb +64 -2
  152. metadata +106 -30
  153. data/TODO +0 -3
  154. data/lib/devise/encryptors/bcrypt.rb +0 -19
  155. data/lib/generators/devise_install_generator.rb +0 -4
  156. data/lib/generators/devise_views_generator.rb +0 -4
  157. data/test/support/test_silencer.rb +0 -5
@@ -1,25 +1,18 @@
1
1
  require 'devise/strategies/database_authenticatable'
2
+ require 'bcrypt'
2
3
 
3
4
  module Devise
4
5
  module Models
5
- # Authenticable Module, responsible for encrypting password and validating
6
+ # Authenticatable Module, responsible for encrypting password and validating
6
7
  # authenticity of a user while signing in.
7
8
  #
8
- # Configuration:
9
+ # == Options
9
10
  #
10
- # You can overwrite configuration values by setting in globally in Devise,
11
- # using devise method or overwriting the respective instance method.
11
+ # DatabaseAuthenticable adds the following options to devise_for:
12
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.
13
+ # * +stretches+: the cost given to bcrypt.
17
14
  #
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
- # Examples:
15
+ # == Examples
23
16
  #
24
17
  # User.find(1).valid_password?('password123') # returns true/false
25
18
  #
@@ -29,22 +22,20 @@ module Devise
29
22
  included do
30
23
  attr_reader :password, :current_password
31
24
  attr_accessor :password_confirmation
25
+ before_save :downcase_keys
32
26
  end
33
27
 
34
- # Regenerates password salt and encrypted password each time password is set,
35
- # and then trigger any "after_changed_password"-callbacks.
28
+ # Generates password encryption based on the given value.
36
29
  def password=(new_password)
37
30
  @password = new_password
38
-
39
- if @password.present?
40
- self.password_salt = self.class.password_salt
41
- self.encrypted_password = password_digest(@password)
42
- end
31
+ self.encrypted_password = password_digest(@password) if @password.present?
43
32
  end
44
33
 
45
- # Verifies whether an incoming_password (ie from sign in) is the user password.
46
- def valid_password?(incoming_password)
47
- password_digest(incoming_password) == self.encrypted_password
34
+ # Verifies whether an password (ie from sign in) is the user password.
35
+ def valid_password?(password)
36
+ bcrypt = ::BCrypt::Password.new(self.encrypted_password)
37
+ password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
38
+ Devise.secure_compare(password, self.encrypted_password)
48
39
  end
49
40
 
50
41
  # Set password and password confirmation to nil
@@ -78,26 +69,25 @@ module Devise
78
69
  def after_database_authentication
79
70
  end
80
71
 
72
+ # A reliable way to expose the salt regardless of the implementation.
73
+ def authenticatable_salt
74
+ self.encrypted_password[0,29] if self.encrypted_password
75
+ end
76
+
81
77
  protected
82
78
 
83
- # Digests the password using the configured encryptor.
79
+ # Downcase case-insensitive keys
80
+ def downcase_keys
81
+ (self.class.case_insensitive_keys || []).each { |k| self[k].try(:downcase!) }
82
+ end
83
+
84
+ # Digests the password using bcrypt.
84
85
  def password_digest(password)
85
- if self.password_salt.present?
86
- self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper)
87
- end
86
+ ::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
88
87
  end
89
88
 
90
89
  module ClassMethods
91
- Devise::Models.config(self, :pepper, :stretches, :encryptor)
92
-
93
- # Returns the class for the configured encryptor.
94
- def encryptor_class
95
- @encryptor_class ||= ::Devise::Encryptors.const_get(encryptor.to_s.classify)
96
- end
97
-
98
- def password_salt
99
- self.encryptor_class.salt(self.stretches)
100
- end
90
+ Devise::Models.config(self, :pepper, :stretches)
101
91
 
102
92
  # We assume this method already gets the sanitized values from the
103
93
  # DatabaseAuthenticatable strategy. If you are using this method on
@@ -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
@@ -7,20 +7,22 @@ module Devise
7
7
  # will unlock the user automatically after some configured time (ie 2.hours).
8
8
  # It's also possible to setup lockable to use both email and time strategies.
9
9
  #
10
- # Configuration:
10
+ # == Options
11
11
  #
12
- # maximum_attempts: how many attempts should be accepted before blocking the user.
13
- # lock_strategy: lock the user account by :failed_attempts or :none.
14
- # unlock_strategy: unlock the user account by :time, :email, :both or :none.
15
- # unlock_in: the time you want to lock the user after to lock happens. Only
16
- # available when unlock_strategy is :time or :both.
12
+ # Lockable adds the following options to devise_for:
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
17
19
  #
18
20
  module Lockable
19
21
  extend ActiveSupport::Concern
20
22
 
21
23
  delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
22
24
 
23
- # Lock an user setting it's locked_at to actual time.
25
+ # Lock a user setting it's locked_at to actual time.
24
26
  def lock_access!
25
27
  self.locked_at = Time.now
26
28
 
@@ -32,14 +34,12 @@ module Devise
32
34
  save(:validate => false)
33
35
  end
34
36
 
35
- # Unlock an user by cleaning locket_at and failed_attempts.
37
+ # Unlock a user by cleaning locket_at and failed_attempts.
36
38
  def unlock_access!
37
- if_access_locked do
38
- self.locked_at = nil
39
- self.failed_attempts = 0 if respond_to?(:failed_attempts=)
40
- self.unlock_token = nil if respond_to?(:unlock_token=)
41
- save(:validate => false)
42
- end
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
43
  end
44
44
 
45
45
  # Verifies whether a user is locked or not.
@@ -57,9 +57,9 @@ module Devise
57
57
  if_access_locked { send_unlock_instructions }
58
58
  end
59
59
 
60
- # Overwrites active? from Devise::Models::Activatable for locking purposes
61
- # by verifying whether an user is active to sign in or not based on locked?
62
- def active?
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
63
  super && !access_locked?
64
64
  end
65
65
 
@@ -70,16 +70,21 @@ module Devise
70
70
  end
71
71
 
72
72
  # Overwrites valid_for_authentication? from Devise::Models::Authenticatable
73
- # for verifying whether an user is allowed to sign in or not. If the user
73
+ # for verifying whether a user is allowed to sign in or not. If the user
74
74
  # is locked, it should never be allowed.
75
75
  def valid_for_authentication?
76
76
  return super unless persisted? && lock_strategy_enabled?(:failed_attempts)
77
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
+
78
82
  case (result = super)
79
83
  when Symbol
80
84
  return result
81
85
  when TrueClass
82
86
  self.failed_attempts = 0
87
+ save(:validate => false)
83
88
  when FalseClass
84
89
  # PostgreSQL uses nil as the default value for integer columns set to 0
85
90
  self.failed_attempts ||= 0
@@ -87,10 +92,11 @@ module Devise
87
92
  if attempts_exceeded?
88
93
  lock_access!
89
94
  return :locked
95
+ else
96
+ save(:validate => false)
90
97
  end
91
98
  end
92
99
 
93
- save(:validate => false) if changed?
94
100
  result
95
101
  end
96
102
 
@@ -131,7 +137,7 @@ module Devise
131
137
  # with an email not found error.
132
138
  # Options must contain the user email
133
139
  def send_unlock_instructions(attributes={})
134
- lockable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
140
+ lockable = find_or_initialize_with_errors(unlock_keys, attributes, :not_found)
135
141
  lockable.resend_unlock_token if lockable.persisted?
136
142
  lockable
137
143
  end
@@ -160,7 +166,7 @@ module Devise
160
166
  Devise.friendly_token
161
167
  end
162
168
 
163
- Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in)
169
+ Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in, :unlock_keys)
164
170
  end
165
171
  end
166
172
  end
@@ -0,0 +1,23 @@
1
+ require 'devise/omniauth'
2
+
3
+ module Devise
4
+ module Models
5
+ # Adds OmniAuth support to your model.
6
+ #
7
+ # == Options
8
+ #
9
+ # Oauthable adds the following options to devise_for:
10
+ #
11
+ # * +omniauth_providers+: Which providers are avaialble to this model. It expects an array:
12
+ #
13
+ # devise_for :database_authenticatable, :omniauthable, :omniauth_providers => [:twitter]
14
+ #
15
+ module Omniauthable
16
+ extend ActiveSupport::Concern
17
+
18
+ module ClassMethods
19
+ Devise::Models.config(self, :omniauth_providers)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,8 +1,15 @@
1
1
  module Devise
2
2
  module Models
3
3
 
4
- # Recoverable takes care of reseting the user password and send reset instructions
5
- # Examples:
4
+ # Recoverable takes care of reseting the user password and send reset instructions.
5
+ #
6
+ # ==Options
7
+ #
8
+ # Recoverable adds the following options to devise_for:
9
+ #
10
+ # * +reset_password_keys+: the keys you want to use when recovering the password for an account
11
+ #
12
+ # == Examples
6
13
  #
7
14
  # # resets the user password and save the record, true if valid passwords are given, otherwise false
8
15
  # User.find(1).reset_password!('password123', 'password123')
@@ -13,6 +20,7 @@ module Devise
13
20
  #
14
21
  # # creates a new token and send it with instructions about how to reset the password
15
22
  # User.find(1).send_reset_password_instructions
23
+ #
16
24
  module Recoverable
17
25
  extend ActiveSupport::Concern
18
26
 
@@ -55,7 +63,7 @@ module Devise
55
63
  # with an email not found error.
56
64
  # Attributes must contain the user email
57
65
  def send_reset_password_instructions(attributes={})
58
- recoverable = find_or_initialize_with_error_by(:email, attributes[:email], :not_found)
66
+ recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
59
67
  recoverable.send_reset_password_instructions if recoverable.persisted?
60
68
  recoverable
61
69
  end
@@ -75,6 +83,8 @@ module Devise
75
83
  recoverable.reset_password!(attributes[:password], attributes[:password_confirmation]) if recoverable.persisted?
76
84
  recoverable
77
85
  end
86
+
87
+ Devise::Models.config(self, :reset_password_keys)
78
88
  end
79
89
  end
80
90
  end
@@ -3,6 +3,19 @@ module Devise
3
3
  # Registerable is responsible for everything related to registering a new
4
4
  # resource (ie user sign up).
5
5
  module Registerable
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # A convenience method that receives both parameters and session to
10
+ # initialize a user. This can be used by OAuth, for example, to send
11
+ # in the user token and be stored on initialization.
12
+ #
13
+ # By default discards all information sent by the session by calling
14
+ # new with params.
15
+ def new_with_session(params, session)
16
+ new(params)
17
+ end
18
+ end
6
19
  end
7
20
  end
8
21
  end
@@ -11,24 +11,27 @@ module Devise
11
11
  # You probably wouldn't use rememberable methods directly, they are used
12
12
  # mostly internally for handling the remember token.
13
13
  #
14
- # Configuration:
14
+ # == Options
15
15
  #
16
- # remember_for: the time you want the user will be remembered without
17
- # asking for credentials. After this time the user will be
18
- # blocked and will have to enter his credentials again.
19
- # This configuration is also used to calculate the expires
20
- # time for the cookie created to remember the user.
21
- # 2.weeks by default.
16
+ # Rememberable adds the following options in devise_for:
22
17
  #
23
- # remember_across_browsers: if true, a valid remember token can be
24
- # re-used between multiple browsers.
25
- # True by default.
18
+ # * +remember_for+: the time you want the user will be remembered without
19
+ # asking for credentials. After this time the user will be blocked and
20
+ # will have to enter his credentials again. This configuration is also
21
+ # used to calculate the expires time for the cookie created to remember
22
+ # the user. By default remember_for is 2.weeks.
26
23
  #
27
- # extend_remember_period: if true, extends the user's remember period
28
- # when remembered via cookie.
29
- # False by default.
24
+ # * +remember_across_browsers+: if a valid remember token can be re-used
25
+ # between multiple browsers. By default remember_across_browsers is true
26
+ # and cannot be turned off if you are using password salt instead of remember
27
+ # token.
30
28
  #
31
- # Examples:
29
+ # * +extend_remember_period+: if true, extends the user's remember period
30
+ # when remembered via cookie. False by default.
31
+ #
32
+ # * +cookie_options+: configuration options passed to the created cookie.
33
+ #
34
+ # == Examples
32
35
  #
33
36
  # User.find(1).remember_me! # regenerating the token
34
37
  # User.find(1).forget_me! # clearing the token
@@ -41,15 +44,12 @@ module Devise
41
44
  module Rememberable
42
45
  extend ActiveSupport::Concern
43
46
 
44
- included do
45
- # Remember me option available in after_authentication hook.
46
- attr_accessor :remember_me
47
- end
47
+ attr_accessor :remember_me, :extend_remember_period
48
48
 
49
49
  # Generate a new remember token and save the record without validations
50
50
  # unless remember_across_browsers is true and the user already has a valid token.
51
51
  def remember_me!(extend_period=false)
52
- self.remember_token = self.class.remember_token if generate_remember_token?
52
+ self.remember_token = self.class.remember_token if respond_to?(:remember_token) && generate_remember_token?
53
53
  self.remember_created_at = Time.now.utc if generate_remember_timestamp?(extend_period)
54
54
  save(:validate => false)
55
55
  end
@@ -57,16 +57,14 @@ module Devise
57
57
  # Removes the remember token only if it exists, and save the record
58
58
  # without validations.
59
59
  def forget_me!
60
- if remember_token
61
- self.remember_token = nil
62
- self.remember_created_at = nil
63
- save(:validate => false)
64
- end
60
+ self.remember_token = nil if respond_to?(:remember_token=)
61
+ self.remember_created_at = nil
62
+ save(:validate => false)
65
63
  end
66
64
 
67
65
  # Remember token should be expired if expiration time not overpass now.
68
66
  def remember_expired?
69
- remember_created_at && (remember_expires_at <= Time.now.utc)
67
+ remember_created_at.nil? || (remember_expires_at <= Time.now.utc)
70
68
  end
71
69
 
72
70
  # Remember token expires at created time + remember_for configuration
@@ -74,12 +72,20 @@ module Devise
74
72
  remember_created_at + self.class.remember_for
75
73
  end
76
74
 
77
- def cookie_domain
78
- self.class.cookie_domain
75
+ def rememberable_value
76
+ if respond_to?(:remember_token)
77
+ remember_token
78
+ elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt)
79
+ salt
80
+ else
81
+ raise "The #{self.class.name} class does not respond to remember_token and " <<
82
+ "authenticatable_salt returns nil. In order to use rememberable, you must " <<
83
+ "add a remember_token field to your model or ensure a password is always set."
84
+ end
79
85
  end
80
86
 
81
- def cookie_domain?
82
- self.class.cookie_domain != false
87
+ def cookie_options
88
+ self.class.cookie_options
83
89
  end
84
90
 
85
91
  protected
@@ -99,14 +105,13 @@ module Devise
99
105
  module ClassMethods
100
106
  # Create the cookie key using the record id and remember_token
101
107
  def serialize_into_cookie(record)
102
- [record.id, record.remember_token]
108
+ [record.to_key, record.rememberable_value]
103
109
  end
104
110
 
105
111
  # Recreate the user based on the stored cookie
106
112
  def serialize_from_cookie(id, remember_token)
107
- conditions = { :id => id, :remember_token => remember_token }
108
- record = find(:first, :conditions => conditions)
109
- record if record && !record.remember_expired?
113
+ record = to_adapter.get(id)
114
+ record if record && record.rememberable_value == remember_token && !record.remember_expired?
110
115
  end
111
116
 
112
117
  # Generate a token checking if one does not already exist in the database.
@@ -115,7 +120,7 @@ module Devise
115
120
  end
116
121
 
117
122
  Devise::Models.config(self, :remember_for, :remember_across_browsers,
118
- :extend_remember_period, :cookie_domain)
123
+ :extend_remember_period, :cookie_options)
119
124
  end
120
125
  end
121
126
  end
@@ -7,17 +7,34 @@ module Devise
7
7
  # will be asked for credentials again, it means, he/she will be redirected
8
8
  # to the sign in page.
9
9
  #
10
- # Configuration:
10
+ # == Options
11
+ #
12
+ # Timeoutable adds the following options to devise_for:
13
+ #
14
+ # * +timeout_in+: the interval to timeout the user session without activity.
15
+ #
16
+ # == Examples
17
+ #
18
+ # user.timedout?(30.minutes.ago)
11
19
  #
12
- # timeout_in: the time you want to timeout the user session without activity.
13
20
  module Timeoutable
14
21
  extend ActiveSupport::Concern
15
22
 
16
23
  # Checks whether the user session has expired based on configured time.
17
24
  def timedout?(last_access)
25
+ return false if remember_exists_and_not_expired?
26
+
18
27
  last_access && last_access <= self.class.timeout_in.ago
19
28
  end
20
-
29
+
30
+ private
31
+
32
+ def remember_exists_and_not_expired?
33
+ return false unless respond_to?(:remember_expired?)
34
+
35
+ remember_created_at && !remember_expired?
36
+ end
37
+
21
38
  module ClassMethods
22
39
  Devise::Models.config(self, :timeout_in)
23
40
  end
@@ -5,15 +5,27 @@ module Devise
5
5
  # The TokenAuthenticatable module is responsible for generating an authentication token and
6
6
  # validating the authenticity of the same while signing in.
7
7
  #
8
- # This module only provides a few helpers to help you manage the token. Creating and resetting
9
- # the token is your responsibility.
8
+ # This module only provides a few helpers to help you manage the token, but it is up to you
9
+ # to choose how to use it. For example, if you want to have a new token every time the user
10
+ # saves his account, you can do the following:
10
11
  #
11
- # == Configuration:
12
+ # before_save :reset_authentication_token
12
13
  #
13
- # You can overwrite configuration values by setting in globally in Devise (+Devise.setup+),
14
- # using devise method, or overwriting the respective instance method.
14
+ # On the other hand, if you want to generate token unless one exists, you should use instead:
15
15
  #
16
- # +token_authentication_key+ - Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
16
+ # before_save :ensure_authentication_token
17
+ #
18
+ # If you want to delete the token after it is used, you can do so in the
19
+ # after_token_authentication callback.
20
+ #
21
+ # == Options
22
+ #
23
+ # TokenAuthenticatable adds the following options to devise_for:
24
+ #
25
+ # * +token_authentication_key+: Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
26
+ #
27
+ # * +stateless_token+: By default, when you sign up with a token, Devise will store the user in session
28
+ # as any other authentication strategy. You can set stateless_token to true to avoid this.
17
29
  #
18
30
  module TokenAuthenticatable
19
31
  extend ActiveSupport::Concern
@@ -26,17 +38,17 @@ module Devise
26
38
  # Generate new authentication token and save the record.
27
39
  def reset_authentication_token!
28
40
  reset_authentication_token
29
- self.save(:validate => false)
41
+ save(:validate => false)
30
42
  end
31
43
 
32
44
  # Generate authentication token unless already exists.
33
45
  def ensure_authentication_token
34
- self.reset_authentication_token if self.authentication_token.blank?
46
+ reset_authentication_token if authentication_token.blank?
35
47
  end
36
48
 
37
49
  # Generate authentication token unless already exists and save the record.
38
50
  def ensure_authentication_token!
39
- self.reset_authentication_token! if self.authentication_token.blank?
51
+ reset_authentication_token! if authentication_token.blank?
40
52
  end
41
53
 
42
54
  # Hook called after token authentication.
@@ -53,7 +65,7 @@ module Devise
53
65
  generate_token(:authentication_token)
54
66
  end
55
67
 
56
- ::Devise::Models.config(self, :token_authentication_key)
68
+ ::Devise::Models.config(self, :token_authentication_key, :stateless_token)
57
69
  end
58
70
  end
59
71
  end
@@ -1,10 +1,17 @@
1
1
  module Devise
2
2
  module Models
3
-
4
3
  # Validatable creates all needed validations for a user email and password.
5
4
  # It's optional, given you may want to create the validations by yourself.
6
5
  # Automatically validate if the email is present, unique and it's format is
7
- # valid. Also tests presence of password, confirmation and length
6
+ # valid. Also tests presence of password, confirmation and length.
7
+ #
8
+ # == Options
9
+ #
10
+ # Validatable adds the following options to devise_for:
11
+ #
12
+ # * +email_regexp+: the regular expression used to validate e-mails;
13
+ # * +password_length+: a range expressing password length. Defaults to 6..20.
14
+ #
8
15
  module Validatable
9
16
  # All validations used by this module.
10
17
  VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of,
@@ -15,8 +22,9 @@ module Devise
15
22
  assert_validations_api!(base)
16
23
 
17
24
  base.class_eval do
18
- validates_presence_of :email
19
- validates_uniqueness_of :email, :scope => authentication_keys[1..-1], :case_sensitive => false, :allow_blank => true
25
+ validates_presence_of :email, :if => :email_required?
26
+ validates_uniqueness_of :email, :scope => authentication_keys[1..-1],
27
+ :case_sensitive => (case_insensitive_keys != false), :allow_blank => true
20
28
  validates_format_of :email, :with => email_regexp, :allow_blank => true
21
29
 
22
30
  with_options :if => :password_required? do |v|
@@ -45,6 +53,10 @@ module Devise
45
53
  !persisted? || !password.nil? || !password_confirmation.nil?
46
54
  end
47
55
 
56
+ def email_required?
57
+ true
58
+ end
59
+
48
60
  module ClassMethods
49
61
  Devise::Models.config(self, :email_regexp, :password_length)
50
62
  end