authlogic 4.4.2 → 5.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +5 -5
  2. data/lib/authlogic.rb +4 -28
  3. data/lib/authlogic/acts_as_authentic/base.rb +3 -18
  4. data/lib/authlogic/acts_as_authentic/email.rb +3 -170
  5. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +3 -1
  6. data/lib/authlogic/acts_as_authentic/login.rb +7 -174
  7. data/lib/authlogic/acts_as_authentic/magic_columns.rb +7 -4
  8. data/lib/authlogic/acts_as_authentic/password.rb +54 -253
  9. data/lib/authlogic/acts_as_authentic/perishable_token.rb +8 -5
  10. data/lib/authlogic/acts_as_authentic/persistence_token.rb +10 -4
  11. data/lib/authlogic/acts_as_authentic/queries/case_sensitivity.rb +53 -0
  12. data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +36 -20
  13. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +8 -6
  14. data/lib/authlogic/acts_as_authentic/single_access_token.rb +10 -8
  15. data/lib/authlogic/config.rb +9 -1
  16. data/lib/authlogic/controller_adapters/abstract_adapter.rb +7 -4
  17. data/lib/authlogic/controller_adapters/rack_adapter.rb +2 -0
  18. data/lib/authlogic/controller_adapters/rails_adapter.rb +19 -19
  19. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +6 -0
  20. data/lib/authlogic/cookie_credentials.rb +63 -0
  21. data/lib/authlogic/crypto_providers.rb +5 -20
  22. data/lib/authlogic/crypto_providers/bcrypt.rb +3 -3
  23. data/lib/authlogic/crypto_providers/md5.rb +3 -6
  24. data/lib/authlogic/crypto_providers/scrypt.rb +2 -0
  25. data/lib/authlogic/crypto_providers/sha1.rb +4 -6
  26. data/lib/authlogic/crypto_providers/sha256.rb +2 -0
  27. data/lib/authlogic/crypto_providers/sha512.rb +6 -5
  28. data/lib/authlogic/i18n.rb +3 -1
  29. data/lib/authlogic/i18n/translator.rb +3 -0
  30. data/lib/authlogic/random.rb +2 -0
  31. data/lib/authlogic/session/base.rb +2087 -39
  32. data/lib/authlogic/session/magic_column/assigns_last_request_at.rb +46 -0
  33. data/lib/authlogic/test_case.rb +4 -0
  34. data/lib/authlogic/test_case/mock_controller.rb +2 -0
  35. data/lib/authlogic/test_case/mock_cookie_jar.rb +7 -0
  36. data/lib/authlogic/test_case/mock_logger.rb +2 -0
  37. data/lib/authlogic/test_case/mock_request.rb +2 -0
  38. data/lib/authlogic/test_case/rails_request_adapter.rb +2 -0
  39. data/lib/authlogic/version.rb +2 -1
  40. metadata +136 -182
  41. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -28
  42. data/.github/ISSUE_TEMPLATE/feature_proposal.md +0 -32
  43. data/.github/triage.md +0 -86
  44. data/.gitignore +0 -15
  45. data/.rubocop.yml +0 -133
  46. data/.rubocop_todo.yml +0 -74
  47. data/.travis.yml +0 -24
  48. data/CHANGELOG.md +0 -326
  49. data/CONTRIBUTING.md +0 -91
  50. data/Gemfile +0 -6
  51. data/LICENSE +0 -20
  52. data/README.md +0 -439
  53. data/Rakefile +0 -21
  54. data/UPGRADING.md +0 -22
  55. data/authlogic.gemspec +0 -40
  56. data/doc/use_normal_rails_validation.md +0 -82
  57. data/gemfiles/Gemfile.rails-4.2.x +0 -6
  58. data/gemfiles/Gemfile.rails-5.1.x +0 -6
  59. data/gemfiles/Gemfile.rails-5.2.x +0 -6
  60. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +0 -106
  61. data/lib/authlogic/acts_as_authentic/validations_scope.rb +0 -35
  62. data/lib/authlogic/authenticates_many/association.rb +0 -50
  63. data/lib/authlogic/authenticates_many/base.rb +0 -81
  64. data/lib/authlogic/crypto_providers/aes256.rb +0 -71
  65. data/lib/authlogic/crypto_providers/wordpress.rb +0 -72
  66. data/lib/authlogic/regex.rb +0 -79
  67. data/lib/authlogic/session/activation.rb +0 -73
  68. data/lib/authlogic/session/active_record_trickery.rb +0 -65
  69. data/lib/authlogic/session/brute_force_protection.rb +0 -127
  70. data/lib/authlogic/session/callbacks.rb +0 -153
  71. data/lib/authlogic/session/cookies.rb +0 -296
  72. data/lib/authlogic/session/existence.rb +0 -103
  73. data/lib/authlogic/session/foundation.rb +0 -105
  74. data/lib/authlogic/session/http_auth.rb +0 -107
  75. data/lib/authlogic/session/id.rb +0 -53
  76. data/lib/authlogic/session/klass.rb +0 -73
  77. data/lib/authlogic/session/magic_columns.rb +0 -119
  78. data/lib/authlogic/session/magic_states.rb +0 -82
  79. data/lib/authlogic/session/params.rb +0 -130
  80. data/lib/authlogic/session/password.rb +0 -318
  81. data/lib/authlogic/session/perishable_token.rb +0 -24
  82. data/lib/authlogic/session/persistence.rb +0 -77
  83. data/lib/authlogic/session/priority_record.rb +0 -38
  84. data/lib/authlogic/session/scopes.rb +0 -138
  85. data/lib/authlogic/session/session.rb +0 -77
  86. data/lib/authlogic/session/timeout.rb +0 -103
  87. data/lib/authlogic/session/unauthorized_record.rb +0 -56
  88. data/lib/authlogic/session/validation.rb +0 -93
  89. data/test/acts_as_authentic_test/base_test.rb +0 -27
  90. data/test/acts_as_authentic_test/email_test.rb +0 -241
  91. data/test/acts_as_authentic_test/logged_in_status_test.rb +0 -64
  92. data/test/acts_as_authentic_test/login_test.rb +0 -153
  93. data/test/acts_as_authentic_test/magic_columns_test.rb +0 -29
  94. data/test/acts_as_authentic_test/password_test.rb +0 -263
  95. data/test/acts_as_authentic_test/perishable_token_test.rb +0 -98
  96. data/test/acts_as_authentic_test/persistence_token_test.rb +0 -62
  97. data/test/acts_as_authentic_test/restful_authentication_test.rb +0 -48
  98. data/test/acts_as_authentic_test/session_maintenance_test.rb +0 -150
  99. data/test/acts_as_authentic_test/single_access_test.rb +0 -46
  100. data/test/adapter_test.rb +0 -23
  101. data/test/authenticates_many_test.rb +0 -33
  102. data/test/config_test.rb +0 -38
  103. data/test/crypto_provider_test/aes256_test.rb +0 -16
  104. data/test/crypto_provider_test/bcrypt_test.rb +0 -16
  105. data/test/crypto_provider_test/scrypt_test.rb +0 -16
  106. data/test/crypto_provider_test/sha1_test.rb +0 -25
  107. data/test/crypto_provider_test/sha256_test.rb +0 -16
  108. data/test/crypto_provider_test/sha512_test.rb +0 -16
  109. data/test/crypto_provider_test/wordpress_test.rb +0 -26
  110. data/test/fixtures/companies.yml +0 -5
  111. data/test/fixtures/employees.yml +0 -17
  112. data/test/fixtures/projects.yml +0 -3
  113. data/test/fixtures/users.yml +0 -41
  114. data/test/i18n/lol.yml +0 -4
  115. data/test/i18n_test.rb +0 -35
  116. data/test/libs/affiliate.rb +0 -9
  117. data/test/libs/company.rb +0 -8
  118. data/test/libs/employee.rb +0 -9
  119. data/test/libs/employee_session.rb +0 -4
  120. data/test/libs/ldaper.rb +0 -5
  121. data/test/libs/project.rb +0 -5
  122. data/test/libs/user.rb +0 -9
  123. data/test/libs/user_session.rb +0 -27
  124. data/test/random_test.rb +0 -15
  125. data/test/session_test/activation_test.rb +0 -45
  126. data/test/session_test/active_record_trickery_test.rb +0 -78
  127. data/test/session_test/brute_force_protection_test.rb +0 -110
  128. data/test/session_test/callbacks_test.rb +0 -42
  129. data/test/session_test/cookies_test.rb +0 -226
  130. data/test/session_test/credentials_test.rb +0 -0
  131. data/test/session_test/existence_test.rb +0 -88
  132. data/test/session_test/foundation_test.rb +0 -24
  133. data/test/session_test/http_auth_test.rb +0 -60
  134. data/test/session_test/id_test.rb +0 -19
  135. data/test/session_test/klass_test.rb +0 -42
  136. data/test/session_test/magic_columns_test.rb +0 -62
  137. data/test/session_test/magic_states_test.rb +0 -60
  138. data/test/session_test/params_test.rb +0 -61
  139. data/test/session_test/password_test.rb +0 -107
  140. data/test/session_test/perishability_test.rb +0 -17
  141. data/test/session_test/persistence_test.rb +0 -35
  142. data/test/session_test/scopes_test.rb +0 -68
  143. data/test/session_test/session_test.rb +0 -80
  144. data/test/session_test/timeout_test.rb +0 -84
  145. data/test/session_test/unauthorized_record_test.rb +0 -15
  146. data/test/session_test/validation_test.rb +0 -25
  147. data/test/test_helper.rb +0 -272
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Authlogic bridge for Sinatra
2
4
  module Authlogic
3
5
  module ControllerAdapters
4
6
  module SinatraAdapter
7
+ # Cookie management functions
5
8
  class Cookies
6
9
  attr_reader :request, :response
7
10
 
@@ -23,6 +26,7 @@ module Authlogic
23
26
  end
24
27
  end
25
28
 
29
+ # Thin wrapper around request and response.
26
30
  class Controller
27
31
  attr_reader :request, :response, :cookies
28
32
 
@@ -40,11 +44,13 @@ module Authlogic
40
44
  end
41
45
  end
42
46
 
47
+ # Sinatra controller adapter
43
48
  class Adapter < AbstractAdapter
44
49
  def cookie_domain
45
50
  env["SERVER_NAME"]
46
51
  end
47
52
 
53
+ # Mixed into `Sinatra::Base`
48
54
  module Implementation
49
55
  def self.included(klass)
50
56
  klass.send :before do
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ # Represents the credentials *in* the cookie. The value of the cookie.
5
+ # This is primarily a data object. It doesn't interact with controllers.
6
+ # It doesn't know about eg. cookie expiration.
7
+ #
8
+ # @api private
9
+ class CookieCredentials
10
+ # @api private
11
+ class ParseError < RuntimeError
12
+ end
13
+
14
+ DELIMITER = "::"
15
+
16
+ attr_reader :persistence_token, :record_id, :remember_me_until
17
+
18
+ # @api private
19
+ # @param persistence_token [String]
20
+ # @param record_id [String, Numeric]
21
+ # @param remember_me_until [ActiveSupport::TimeWithZone]
22
+ def initialize(persistence_token, record_id, remember_me_until)
23
+ @persistence_token = persistence_token
24
+ @record_id = record_id
25
+ @remember_me_until = remember_me_until
26
+ end
27
+
28
+ class << self
29
+ # @api private
30
+ def parse(string)
31
+ parts = string.split(DELIMITER)
32
+ unless (1..3).cover?(parts.length)
33
+ raise ParseError, format("Expected 1..3 parts, got %d", parts.length)
34
+ end
35
+ new(parts[0], parts[1], parse_time(parts[2]))
36
+ end
37
+
38
+ private
39
+
40
+ # @api private
41
+ def parse_time(string)
42
+ return if string.nil?
43
+ ::Time.parse(string)
44
+ rescue ::ArgumentError => e
45
+ raise ParseError, format("Found cookie, cannot parse remember_me_until: #{e}")
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ def remember_me?
51
+ !@remember_me_until.nil?
52
+ end
53
+
54
+ # @api private
55
+ def to_s
56
+ [
57
+ @persistence_token,
58
+ @record_id.to_s,
59
+ @remember_me_until&.iso8601
60
+ ].compact.join(DELIMITER)
61
+ end
62
+ end
63
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Authlogic
2
4
  # The acts_as_authentic method has a crypto_provider option. This allows you
3
5
  # to use any type of encryption you like. Just create a class with a class
@@ -26,27 +28,12 @@ module Authlogic
26
28
  autoload :Sha256, "authlogic/crypto_providers/sha256"
27
29
  autoload :Sha512, "authlogic/crypto_providers/sha512"
28
30
  autoload :BCrypt, "authlogic/crypto_providers/bcrypt"
29
- autoload :AES256, "authlogic/crypto_providers/aes256"
30
31
  autoload :SCrypt, "authlogic/crypto_providers/scrypt"
31
- # crypto_providers/wordpress.rb has never been autoloaded, and now it is
32
- # deprecated.
33
32
 
34
33
  # Guide users to choose a better crypto provider.
35
34
  class Guidance
36
- AES256_DEPRECATED = <<~EOS.freeze
37
- You have selected AES256 as your authlogic crypto provider. This
38
- choice is not suitable for password storage.
39
-
40
- Authlogic will drop its AES256 crypto provider in the next major
41
- version. If you're unable to transition away from AES256 please let us
42
- know immediately.
43
-
44
- We recommend using a one-way algorithm instead. There are many choices;
45
- we recommend scrypt. Use the transition_from_crypto_providers option
46
- to make this painless for your users.
47
- EOS
48
- BUILTIN_PROVIDER_PREFIX = "Authlogic::CryptoProviders::".freeze
49
- NONADAPTIVE_ALGORITHM = <<~EOS.freeze
35
+ BUILTIN_PROVIDER_PREFIX = "Authlogic::CryptoProviders::"
36
+ NONADAPTIVE_ALGORITHM = <<~EOS
50
37
  You have selected %s as your authlogic crypto provider. This algorithm
51
38
  does not have any practical known attacks against it. However, there are
52
39
  better choices.
@@ -61,7 +48,7 @@ module Authlogic
61
48
  Use the transition_from_crypto_providers option to make the transition
62
49
  painless for your users.
63
50
  EOS
64
- VULNERABLE_ALGORITHM = <<~EOS.freeze
51
+ VULNERABLE_ALGORITHM = <<~EOS
65
52
  You have selected %s as your authlogic crypto provider. It is a poor
66
53
  choice because there are known attacks against this algorithm.
67
54
 
@@ -89,8 +76,6 @@ module Authlogic
89
76
  # negate the benefits of the `autoload` above.
90
77
  name = absolute_name.demodulize
91
78
  case name
92
- when "AES256"
93
- ::ActiveSupport::Deprecation.warn(AES256_DEPRECATED)
94
79
  when "MD5", "Sha1"
95
80
  warn(format(VULNERABLE_ALGORITHM, name))
96
81
  when "Sha256", "Sha512"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bcrypt"
2
4
 
3
5
  module Authlogic
@@ -64,10 +66,8 @@ module Authlogic
64
66
 
65
67
  def cost=(val)
66
68
  if val < ::BCrypt::Engine::MIN_COST
67
- raise ArgumentError.new(
68
- "Authlogic's bcrypt cost cannot be set below the engine's " \
69
+ raise ArgumentError, "Authlogic's bcrypt cost cannot be set below the engine's " \
69
70
  "min cost (#{::BCrypt::Engine::MIN_COST})"
70
- )
71
71
  end
72
72
  @cost = val
73
73
  end
@@ -1,13 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "digest/md5"
2
4
 
3
5
  module Authlogic
4
6
  module CryptoProviders
5
- # This class was made for the users transitioning from md5 based systems.
6
- # I highly discourage using this crypto provider as it superbly inferior
7
- # to your other options.
8
- #
9
- # Please use any other provider offered by Authlogic (except AES256, that
10
- # would be even worse).
7
+ # A poor choice. There are known attacks against this algorithm.
11
8
  class MD5
12
9
  class << self
13
10
  attr_accessor :join_token
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "scrypt"
2
4
 
3
5
  module Authlogic
@@ -1,11 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "digest/sha1"
2
4
 
3
5
  module Authlogic
4
6
  module CryptoProviders
5
- # This class was made for the users transitioning from
6
- # restful_authentication. Use of this crypto provider is highly discouraged.
7
- # It is far inferior to your other options. Please use any other provider
8
- # offered by Authlogic.
7
+ # A poor choice. There are known attacks against this algorithm.
9
8
  class Sha1
10
9
  class << self
11
10
  def join_token
@@ -13,8 +12,7 @@ module Authlogic
13
12
  end
14
13
  attr_writer :join_token
15
14
 
16
- # The number of times to loop through the encryption. This is ten
17
- # because that is what restful_authentication defaults to.
15
+ # The number of times to loop through the encryption.
18
16
  def stretches
19
17
  @stretches ||= 10
20
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "digest/sha2"
2
4
 
3
5
  module Authlogic
@@ -1,16 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "digest/sha2"
2
4
 
3
5
  module Authlogic
4
6
  module CryptoProviders
5
- # = Sha512
6
- #
7
- # Uses the Sha512 hash algorithm to encrypt passwords.
7
+ # SHA-512 does not have any practical known attacks against it. However,
8
+ # there are better choices. We recommend transitioning to a more secure,
9
+ # adaptive hashing algorithm, like scrypt.
8
10
  class Sha512
9
11
  class << self
10
12
  attr_accessor :join_token
11
13
 
12
- # The number of times to loop through the encryption. This is twenty
13
- # because that is what restful_authentication defaults to.
14
+ # The number of times to loop through the encryption.
14
15
  def stretches
15
16
  @stretches ||= 20
16
17
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "authlogic/i18n/translator"
2
4
 
3
5
  module Authlogic
@@ -92,7 +94,7 @@ module Authlogic
92
94
  def translate(key, options = {})
93
95
  translator.translate key, { scope: I18n.scope }.merge(options)
94
96
  end
95
- alias :t :translate
97
+ alias t translate
96
98
  end
97
99
  end
98
100
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Authlogic
2
4
  module I18n
5
+ # The default translator used by authlogic/i18n.rb
3
6
  class Translator
4
7
  # If the I18n gem is present, calls +I18n.translate+ passing all
5
8
  # arguments, else returns +options[:default]+.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "securerandom"
2
4
 
3
5
  module Authlogic
@@ -1,49 +1,2097 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "request_store"
4
+
1
5
  module Authlogic
2
- module Session # :nodoc:
6
+ module Session
7
+ module Activation
8
+ # :nodoc:
9
+ class NotActivatedError < ::StandardError
10
+ def initialize
11
+ super(
12
+ "You must activate the Authlogic::Session::Base.controller with " \
13
+ "a controller object before creating objects"
14
+ )
15
+ end
16
+ end
17
+ end
18
+
19
+ module Existence
20
+ # :nodoc:
21
+ class SessionInvalidError < ::StandardError
22
+ def initialize(session)
23
+ message = I18n.t(
24
+ "error_messages.session_invalid",
25
+ default: "Your session is invalid and has the following errors:"
26
+ )
27
+ message += " #{session.errors.full_messages.to_sentence}"
28
+ super message
29
+ end
30
+ end
31
+ end
32
+
3
33
  # This is the most important class in Authlogic. You will inherit this class
4
34
  # for your own eg. `UserSession`.
5
35
  #
6
- # Code is organized topically. Each topic is represented by a module. So, to
7
- # learn about password-based authentication, read the `Password` module.
36
+ # Ongoing consolidation of modules
37
+ # ================================
38
+ #
39
+ # We are consolidating modules into this class (inlining mixins). When we
40
+ # are done, there will only be this one file. It will be quite large, but it
41
+ # will be easier to trace execution.
42
+ #
43
+ # Once consolidation is complete, we hope to identify and extract
44
+ # collaborating objects. For example, there may be a "session adapter" that
45
+ # connects this class with the existing `ControllerAdapters`. Perhaps a
46
+ # data object or a state machine will reveal itself.
47
+ #
48
+ # Activation
49
+ # ==========
50
+ #
51
+ # Activating Authlogic requires that you pass it an
52
+ # Authlogic::ControllerAdapters::AbstractAdapter object, or a class that
53
+ # extends it. This is sort of like a database connection for an ORM library,
54
+ # Authlogic can't do anything until it is "connected" to a controller. If
55
+ # you are using a supported framework, Authlogic takes care of this for you.
56
+ #
57
+ # ActiveRecord Trickery
58
+ # =====================
59
+ #
60
+ # Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not
61
+ # ActiveRecord. That's the goal here. This is useful for the various rails
62
+ # helper methods such as form_for, error_messages_for, or any method that
63
+ # expects an ActiveRecord object. The point is to disguise the object as an
64
+ # ActiveRecord object so we can take advantage of the many ActiveRecord
65
+ # tools.
66
+ #
67
+ # Brute Force Protection
68
+ # ======================
69
+ #
70
+ # A brute force attacks is executed by hammering a login with as many password
71
+ # combinations as possible, until one works. A brute force attacked is generally
72
+ # combated with a slow hashing algorithm such as BCrypt. You can increase the cost,
73
+ # which makes the hash generation slower, and ultimately increases the time it takes
74
+ # to execute a brute force attack. Just to put this into perspective, if a hacker was
75
+ # to gain access to your server and execute a brute force attack locally, meaning
76
+ # there is no network lag, it would probably take decades to complete. Now throw in
77
+ # network lag and it would take MUCH longer.
78
+ #
79
+ # But for those that are extra paranoid and can't get enough protection, why not stop
80
+ # them as soon as you realize something isn't right? That's what this module is all
81
+ # about. By default the consecutive_failed_logins_limit configuration option is set to
82
+ # 50, if someone consecutively fails to login after 50 attempts their account will be
83
+ # suspended. This is a very liberal number and at this point it should be obvious that
84
+ # something is not right. If you wish to lower this number just set the configuration
85
+ # to a lower number:
86
+ #
87
+ # class UserSession < Authlogic::Session::Base
88
+ # consecutive_failed_logins_limit 10
89
+ # end
90
+ #
91
+ # Callbacks
92
+ # =========
93
+ #
94
+ # Between these callbacks and the configuration, this is the contract between me and
95
+ # you to safely modify Authlogic's behavior. I will do everything I can to make sure
96
+ # these do not change.
97
+ #
98
+ # Check out the sub modules of Authlogic::Session. They are very concise, clear, and
99
+ # to the point. More importantly they use the same API that you would use to extend
100
+ # Authlogic. That being said, they are great examples of how to extend Authlogic and
101
+ # add / modify behavior to Authlogic. These modules could easily be pulled out into
102
+ # their own plugin and become an "add on" without any change.
103
+ #
104
+ # Now to the point of this module. Just like in ActiveRecord you have before_save,
105
+ # before_validation, etc. You have similar callbacks with Authlogic, see the METHODS
106
+ # constant below. The order of execution is as follows:
107
+ #
108
+ # before_persisting
109
+ # persist
110
+ # after_persisting
111
+ # [save record if record.has_changes_to_save?]
112
+ #
113
+ # before_validation
114
+ # before_validation_on_create
115
+ # before_validation_on_update
116
+ # validate
117
+ # after_validation_on_update
118
+ # after_validation_on_create
119
+ # after_validation
120
+ # [save record if record.has_changes_to_save?]
121
+ #
122
+ # before_save
123
+ # before_create
124
+ # before_update
125
+ # after_update
126
+ # after_create
127
+ # after_save
128
+ # [save record if record.has_changes_to_save?]
129
+ #
130
+ # before_destroy
131
+ # [save record if record.has_changes_to_save?]
132
+ # after_destroy
133
+ #
134
+ # Notice the "save record if has_changes_to_save" lines above. This helps with performance. If
135
+ # you need to make changes to the associated record, there is no need to save the
136
+ # record, Authlogic will do it for you. This allows multiple modules to modify the
137
+ # record and execute as few queries as possible.
138
+ #
139
+ # **WARNING**: unlike ActiveRecord, these callbacks must be set up on the class level:
140
+ #
141
+ # class UserSession < Authlogic::Session::Base
142
+ # before_validation :my_method
143
+ # validate :another_method
144
+ # # ..etc
145
+ # end
146
+ #
147
+ # You can NOT define a "before_validation" method, this is bad practice and does not
148
+ # allow Authlogic to extend properly with multiple extensions. Please ONLY use the
149
+ # method above.
150
+ #
151
+ # HTTP Basic Authentication
152
+ # =========================
153
+ #
154
+ # Handles all authentication that deals with basic HTTP auth. Which is
155
+ # authentication built into the HTTP protocol:
156
+ #
157
+ # http://username:password@whatever.com
158
+ #
159
+ # Also, if you are not comfortable letting users pass their raw username and
160
+ # password you can use a single access token, as described below.
161
+ #
162
+ # Magic Columns
163
+ # =============
164
+ #
165
+ # Just like ActiveRecord has "magic" columns, such as: created_at and updated_at.
166
+ # Authlogic has its own "magic" columns too:
167
+ #
168
+ # * login_count - Increased every time an explicit login is made. This will *NOT*
169
+ # increase if logging in by a session, cookie, or basic http auth
170
+ # * failed_login_count - This increases for each consecutive failed login. See
171
+ # the consecutive_failed_logins_limit option for details.
172
+ # * last_request_at - Updates every time the user logs in, either by explicitly
173
+ # logging in, or logging in by cookie, session, or http auth
174
+ # * current_login_at - Updates with the current time when an explicit login is made.
175
+ # * last_login_at - Updates with the value of current_login_at before it is reset.
176
+ # * current_login_ip - Updates with the request ip when an explicit login is made.
177
+ # * last_login_ip - Updates with the value of current_login_ip before it is reset.
178
+ #
179
+ # Multiple Simultaneous Sessions
180
+ # ==============================
181
+ #
182
+ # See `id`. Allows you to separate sessions with an id, ultimately letting
183
+ # you create multiple sessions for the same user.
184
+ #
185
+ # Timeout
186
+ # =======
187
+ #
188
+ # Think about financial websites, if you are inactive for a certain period
189
+ # of time you will be asked to log back in on your next request. You can do
190
+ # this with Authlogic easily, there are 2 parts to this:
191
+ #
192
+ # 1. Define the timeout threshold:
193
+ #
194
+ # acts_as_authentic do |c|
195
+ # c.logged_in_timeout = 10.minutes # default is 10.minutes
196
+ # end
197
+ #
198
+ # 2. Enable logging out on timeouts
199
+ #
200
+ # class UserSession < Authlogic::Session::Base
201
+ # logout_on_timeout true # default if false
202
+ # end
203
+ #
204
+ # This will require a user to log back in if they are inactive for more than
205
+ # 10 minutes. In order for this feature to be used you must have a
206
+ # last_request_at datetime column in your table for whatever model you are
207
+ # authenticating with.
208
+ #
209
+ # Params
210
+ # ======
211
+ #
212
+ # This module is responsible for authenticating the user via params, which ultimately
213
+ # allows the user to log in using a URL like the following:
214
+ #
215
+ # https://www.domain.com?user_credentials=4LiXF7FiGUppIPubBPey
216
+ #
217
+ # Notice the token in the URL, this is a single access token. A single access token is
218
+ # used for single access only, it is not persisted. Meaning the user provides it,
219
+ # Authlogic grants them access, and that's it. If they want access again they need to
220
+ # provide the token again. Authlogic will *NEVER* try to persist the session after
221
+ # authenticating through this method.
222
+ #
223
+ # For added security, this token is *ONLY* allowed for RSS and ATOM requests. You can
224
+ # change this with the configuration. You can also define if it is allowed dynamically
225
+ # by defining a single_access_allowed? method in your controller. For example:
226
+ #
227
+ # class UsersController < ApplicationController
228
+ # private
229
+ # def single_access_allowed?
230
+ # action_name == "index"
231
+ # end
232
+ #
233
+ # Also, by default, this token is permanent. Meaning if the user changes their
234
+ # password, this token will remain the same. It will only change when it is explicitly
235
+ # reset.
236
+ #
237
+ # You can modify all of this behavior with the Config sub module.
238
+ #
239
+ # Perishable Token
240
+ # ================
241
+ #
242
+ # Maintains the perishable token, which is helpful for confirming records or
243
+ # authorizing records to reset their password. All that this module does is
244
+ # reset it after a session have been saved, just keep it changing. The more
245
+ # it changes, the tighter the security.
246
+ #
247
+ # See Authlogic::ActsAsAuthentic::PerishableToken for more information.
248
+ #
249
+ # Scopes
250
+ # ======
251
+ #
252
+ # Authentication can be scoped, and it's easy, you just need to define how you want to
253
+ # scope everything. See `.with_scope`.
254
+ #
255
+ # Unauthorized Record
256
+ # ===================
257
+ #
258
+ # Allows you to create session with an object. Ex:
259
+ #
260
+ # UserSession.create(my_user_object)
261
+ #
262
+ # Be careful with this, because Authlogic is assuming that you have already
263
+ # confirmed that the user is who he says he is.
264
+ #
265
+ # For example, this is the method used to persist the session internally.
266
+ # Authlogic finds the user with the persistence token. At this point we know
267
+ # the user is who he says he is, so Authlogic just creates a session with
268
+ # the record. This is particularly useful for 3rd party authentication
269
+ # methods, such as OpenID. Let that method verify the identity, once it's
270
+ # verified, pass the object and create a session.
271
+ #
272
+ # Magic States
273
+ # ============
274
+ #
275
+ # Authlogic tries to check the state of the record before creating the session. If
276
+ # your record responds to the following methods and any of them return false,
277
+ # validation will fail:
278
+ #
279
+ # Method name Description
280
+ # active? Is the record marked as active?
281
+ # approved? Has the record been approved?
282
+ # confirmed? Has the record been confirmed?
283
+ #
284
+ # Authlogic does nothing to define these methods for you, its up to you to define what
285
+ # they mean. If your object responds to these methods Authlogic will use them,
286
+ # otherwise they are ignored.
287
+ #
288
+ # What's neat about this is that these are checked upon any type of login. When
289
+ # logging in explicitly, by cookie, session, or basic http auth. So if you mark a user
290
+ # inactive in the middle of their session they wont be logged back in next time they
291
+ # refresh the page. Giving you complete control.
8
292
  #
9
- # It is common for methods (.initialize and #credentials=, for example) to
10
- # be implemented in multiple mixins. Those methods will call `super`, so the
11
- # order of `include`s here is important.
293
+ # Need Authlogic to check your own "state"? No problem, check out the hooks section
294
+ # below. Add in a before_validation to do your own checking. The sky is the limit.
12
295
  #
13
- # Also, to fully understand such a method (like #credentials=) you will need
14
- # to mentally combine all of its definitions. This is perhaps the primary
15
- # disadvantage of topical organization using modules.
296
+ # Validation
297
+ # ==========
298
+ #
299
+ # The errors in Authlogic work just like ActiveRecord. In fact, it uses
300
+ # the `ActiveModel::Errors` class. Use it the same way:
301
+ #
302
+ # ```
303
+ # class UserSession
304
+ # validate :check_if_awesome
305
+ #
306
+ # private
307
+ #
308
+ # def check_if_awesome
309
+ # if login && !login.include?("awesome")
310
+ # errors.add(:login, "must contain awesome")
311
+ # end
312
+ # unless attempted_record.awesome?
313
+ # errors.add(:base, "You must be awesome to log in")
314
+ # end
315
+ # end
316
+ # end
317
+ # ```
16
318
  class Base
17
- include Foundation
18
- include Callbacks
19
-
20
- # Included first so that the session resets itself to nil
21
- include Timeout
22
-
23
- # Included in a specific order so they are tried in this order when persisting
24
- include Params
25
- include Cookies
26
- include Session
27
- include HttpAuth
28
-
29
- # Included in a specific order so magic states gets run after a record is found
30
- # TODO: What does "magic states gets run" mean? Be specific.
31
- include Password
32
- include UnauthorizedRecord
33
- include MagicStates
34
-
35
- include Activation
36
- include ActiveRecordTrickery
37
- include BruteForceProtection
38
- include Existence
39
- include Klass
40
- include MagicColumns
41
- include PerishableToken
42
- include Persistence
43
- include Scopes
44
- include Id
45
- include Validation
46
- include PriorityRecord
319
+ extend ActiveModel::Naming
320
+ extend ActiveModel::Translation
321
+ extend Authlogic::Config
322
+ include ActiveSupport::Callbacks
323
+
324
+ E_AC_PARAMETERS = <<~EOS
325
+ Passing an ActionController::Parameters to Authlogic is not allowed.
326
+
327
+ In Authlogic 3, especially during the transition of rails to Strong
328
+ Parameters, it was common for Authlogic users to forget to `permit`
329
+ their params. They would pass their params into Authlogic, we'd call
330
+ `to_h`, and they'd be surprised when authentication failed.
331
+
332
+ In 2018, people are still making this mistake. We'd like to help them
333
+ and make authlogic a little simpler at the same time, so in Authlogic
334
+ 3.7.0, we deprecated the use of ActionController::Parameters. Instead,
335
+ pass a plain Hash. Please replace:
336
+
337
+ UserSession.new(user_session_params)
338
+ UserSession.create(user_session_params)
339
+
340
+ with
341
+
342
+ UserSession.new(user_session_params.to_h)
343
+ UserSession.create(user_session_params.to_h)
344
+
345
+ And don't forget to `permit`!
346
+
347
+ We discussed this issue thoroughly between late 2016 and early
348
+ 2018. Notable discussions include:
349
+
350
+ - https://github.com/binarylogic/authlogic/issues/512
351
+ - https://github.com/binarylogic/authlogic/pull/558
352
+ - https://github.com/binarylogic/authlogic/pull/577
353
+ EOS
354
+ VALID_SAME_SITE_VALUES = [nil, "Lax", "Strict"].freeze
355
+
356
+ # Callbacks
357
+ # =========
358
+
359
+ METHODS = %w[
360
+ before_persisting
361
+ persist
362
+ after_persisting
363
+ before_validation
364
+ before_validation_on_create
365
+ before_validation_on_update
366
+ validate
367
+ after_validation_on_update
368
+ after_validation_on_create
369
+ after_validation
370
+ before_save
371
+ before_create
372
+ before_update
373
+ after_update
374
+ after_create
375
+ after_save
376
+ before_destroy
377
+ after_destroy
378
+ ].freeze
379
+
380
+ # Defines the "callback installation methods" used below.
381
+ METHODS.each do |method|
382
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
383
+ def self.#{method}(*filter_list, &block)
384
+ set_callback(:#{method}, *filter_list, &block)
385
+ end
386
+ EOS
387
+ end
388
+
389
+ # Defines session life cycle events that support callbacks.
390
+ define_callbacks(
391
+ *METHODS,
392
+ terminator: ->(_target, result_lambda) { result_lambda.call == false }
393
+ )
394
+ define_callbacks(
395
+ "persist",
396
+ terminator: ->(_target, result_lambda) { result_lambda.call == true }
397
+ )
398
+
399
+ # Use the "callback installation methods" defined above
400
+ # -----------------------------------------------------
401
+
402
+ before_persisting :reset_stale_state
403
+
404
+ # `persist` callbacks, in order of priority
405
+ persist :persist_by_params
406
+ persist :persist_by_cookie
407
+ persist :persist_by_session
408
+ persist :persist_by_http_auth, if: :persist_by_http_auth?
409
+
410
+ after_persisting :enforce_timeout
411
+ after_persisting :update_session, unless: :single_access?
412
+ after_persisting :set_last_request_at
413
+
414
+ before_save :update_info
415
+ before_save :set_last_request_at
416
+
417
+ after_save :reset_perishable_token!
418
+ after_save :save_cookie
419
+ after_save :update_session
420
+
421
+ after_destroy :destroy_cookie
422
+ after_destroy :update_session
423
+
424
+ # `validate` callbacks, in deliberate order. For example,
425
+ # validate_magic_states must run *after* a record is found.
426
+ validate :validate_by_password, if: :authenticating_with_password?
427
+ validate(
428
+ :validate_by_unauthorized_record,
429
+ if: :authenticating_with_unauthorized_record?
430
+ )
431
+ validate :validate_magic_states, unless: :disable_magic_states?
432
+ validate :reset_failed_login_count, if: :reset_failed_login_count?
433
+ validate :validate_failed_logins, if: :being_brute_force_protected?
434
+ validate :increase_failed_login_count
435
+
436
+ # Accessors
437
+ # =========
438
+
439
+ class << self
440
+ attr_accessor(
441
+ :configured_password_methods,
442
+ :configured_klass_methods
443
+ )
444
+ end
445
+ attr_accessor(
446
+ :invalid_password,
447
+ :new_session,
448
+ :priority_record,
449
+ :record,
450
+ :single_access,
451
+ :stale_record,
452
+ :unauthorized_record
453
+ )
454
+ attr_writer(
455
+ :scope,
456
+ :id
457
+ )
458
+
459
+ # Public class methods
460
+ # ====================
461
+
462
+ class << self
463
+ # Returns true if a controller has been set and can be used properly.
464
+ # This MUST be set before anything can be done. Similar to how
465
+ # ActiveRecord won't allow you to do anything without establishing a DB
466
+ # connection. In your framework environment this is done for you, but if
467
+ # you are using Authlogic outside of your framework, you need to assign
468
+ # a controller object to Authlogic via
469
+ # Authlogic::Session::Base.controller = obj. See the controller= method
470
+ # for more information.
471
+ def activated?
472
+ !controller.nil?
473
+ end
474
+
475
+ # Do you want to allow your users to log in via HTTP basic auth?
476
+ #
477
+ # I recommend keeping this enabled. The only time I feel this should be
478
+ # disabled is if you are not comfortable having your users provide their
479
+ # raw username and password. Whatever the reason, you can disable it
480
+ # here.
481
+ #
482
+ # * <tt>Default:</tt> true
483
+ # * <tt>Accepts:</tt> Boolean
484
+ def allow_http_basic_auth(value = nil)
485
+ rw_config(:allow_http_basic_auth, value, false)
486
+ end
487
+ alias allow_http_basic_auth= allow_http_basic_auth
488
+
489
+ # Lets you change which model to use for authentication.
490
+ #
491
+ # * <tt>Default:</tt> inferred from the class name. UserSession would
492
+ # automatically try User
493
+ # * <tt>Accepts:</tt> an ActiveRecord class
494
+ def authenticate_with(klass)
495
+ @klass_name = klass.name
496
+ @klass = klass
497
+ end
498
+ alias authenticate_with= authenticate_with
499
+
500
+ # The current controller object
501
+ def controller
502
+ RequestStore.store[:authlogic_controller]
503
+ end
504
+
505
+ # This accepts a controller object wrapped with the Authlogic controller
506
+ # adapter. The controller adapters close the gap between the different
507
+ # controllers in each framework. That being said, Authlogic is expecting
508
+ # your object's class to extend
509
+ # Authlogic::ControllerAdapters::AbstractAdapter. See
510
+ # Authlogic::ControllerAdapters for more info.
511
+ #
512
+ # Lastly, this is thread safe.
513
+ def controller=(value)
514
+ RequestStore.store[:authlogic_controller] = value
515
+ end
516
+
517
+ # To help protect from brute force attacks you can set a limit on the
518
+ # allowed number of consecutive failed logins. By default this is 50,
519
+ # this is a very liberal number, and if someone fails to login after 50
520
+ # tries it should be pretty obvious that it's a machine trying to login
521
+ # in and very likely a brute force attack.
522
+ #
523
+ # In order to enable this field your model MUST have a
524
+ # failed_login_count (integer) field.
525
+ #
526
+ # If you don't know what a brute force attack is, it's when a machine
527
+ # tries to login into a system using every combination of character
528
+ # possible. Thus resulting in possibly millions of attempts to log into
529
+ # an account.
530
+ #
531
+ # * <tt>Default:</tt> 50
532
+ # * <tt>Accepts:</tt> Integer, set to 0 to disable
533
+ def consecutive_failed_logins_limit(value = nil)
534
+ rw_config(:consecutive_failed_logins_limit, value, 50)
535
+ end
536
+ alias consecutive_failed_logins_limit= consecutive_failed_logins_limit
537
+
538
+ # The name of the cookie or the key in the cookies hash. Be sure and use
539
+ # a unique name. If you have multiple sessions and they use the same
540
+ # cookie it will cause problems. Also, if a id is set it will be
541
+ # inserted into the beginning of the string. Example:
542
+ #
543
+ # session = UserSession.new
544
+ # session.cookie_key => "user_credentials"
545
+ #
546
+ # session = UserSession.new(:super_high_secret)
547
+ # session.cookie_key => "super_high_secret_user_credentials"
548
+ #
549
+ # * <tt>Default:</tt> "#{klass_name.underscore}_credentials"
550
+ # * <tt>Accepts:</tt> String
551
+ def cookie_key(value = nil)
552
+ rw_config(:cookie_key, value, "#{klass_name.underscore}_credentials")
553
+ end
554
+ alias cookie_key= cookie_key
555
+
556
+ # A convenience method. The same as:
557
+ #
558
+ # session = UserSession.new(*args)
559
+ # session.save
560
+ #
561
+ # Instead you can do:
562
+ #
563
+ # UserSession.create(*args)
564
+ def create(*args, &block)
565
+ session = new(*args)
566
+ session.save(&block)
567
+ session
568
+ end
569
+
570
+ # Same as create but calls create!, which raises an exception when
571
+ # validation fails.
572
+ def create!(*args)
573
+ session = new(*args)
574
+ session.save!
575
+ session
576
+ end
577
+
578
+ # Set this to true if you want to disable the checking of active?, approved?, and
579
+ # confirmed? on your record. This is more or less of a convenience feature, since
580
+ # 99% of the time if those methods exist and return false you will not want the
581
+ # user logging in. You could easily accomplish this same thing with a
582
+ # before_validation method or other callbacks.
583
+ #
584
+ # * <tt>Default:</tt> false
585
+ # * <tt>Accepts:</tt> Boolean
586
+ def disable_magic_states(value = nil)
587
+ rw_config(:disable_magic_states, value, false)
588
+ end
589
+ alias disable_magic_states= disable_magic_states
590
+
591
+ # Once the failed logins limit has been exceed, how long do you want to
592
+ # ban the user? This can be a temporary or permanent ban.
593
+ #
594
+ # * <tt>Default:</tt> 2.hours
595
+ # * <tt>Accepts:</tt> Fixnum, set to 0 for permanent ban
596
+ def failed_login_ban_for(value = nil)
597
+ rw_config(:failed_login_ban_for, (!value.nil? && value) || value, 2.hours.to_i)
598
+ end
599
+ alias failed_login_ban_for= failed_login_ban_for
600
+
601
+ # This is how you persist a session. This finds the record for the
602
+ # current session using a variety of methods. It basically tries to "log
603
+ # in" the user without the user having to explicitly log in. Check out
604
+ # the other Authlogic::Session modules for more information.
605
+ #
606
+ # The best way to use this method is something like:
607
+ #
608
+ # helper_method :current_user_session, :current_user
609
+ #
610
+ # def current_user_session
611
+ # return @current_user_session if defined?(@current_user_session)
612
+ # @current_user_session = UserSession.find
613
+ # end
614
+ #
615
+ # def current_user
616
+ # return @current_user if defined?(@current_user)
617
+ # @current_user = current_user_session && current_user_session.user
618
+ # end
619
+ #
620
+ # Also, this method accepts a single parameter as the id, to find
621
+ # session that you marked with an id:
622
+ #
623
+ # UserSession.find(:secure)
624
+ #
625
+ # See the id method for more information on ids.
626
+ #
627
+ # Priority Record
628
+ # ===============
629
+ #
630
+ # This internal feature supports ActiveRecord's optimistic locking feature,
631
+ # which is automatically enabled when a table has a `lock_version` column.
632
+ #
633
+ # ```
634
+ # # https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
635
+ # p1 = Person.find(1)
636
+ # p2 = Person.find(1)
637
+ # p1.first_name = "Michael"
638
+ # p1.save
639
+ # p2.first_name = "should fail"
640
+ # p2.save # Raises an ActiveRecord::StaleObjectError
641
+ # ```
642
+ #
643
+ # Now, consider the following Authlogic scenario:
644
+ #
645
+ # ```
646
+ # User.log_in_after_password_change = true
647
+ # ben = User.find(1)
648
+ # UserSession.create(ben)
649
+ # ben.password = "newpasswd"
650
+ # ben.password_confirmation = "newpasswd"
651
+ # ben.save
652
+ # ```
653
+ #
654
+ # We've used one of Authlogic's session maintenance features,
655
+ # `log_in_after_password_change`. So, when we call `ben.save`, there is a
656
+ # `before_save` callback that logs Ben in (`UserSession.find`). Well, when
657
+ # we log Ben in, we update his user record, eg. `login_count`. When we're
658
+ # done logging Ben in, then the normal `ben.save` happens. So, there were
659
+ # two `update` queries. If those two updates came from different User
660
+ # instances, we would get a `StaleObjectError`.
661
+ #
662
+ # Our solution is to carefully pass around a single `User` instance, using
663
+ # it for all `update` queries, thus avoiding the `StaleObjectError`.
664
+ def find(id = nil, priority_record = nil)
665
+ session = new({ priority_record: priority_record }, id)
666
+ session.priority_record = priority_record
667
+ if session.persisting?
668
+ session
669
+ end
670
+ end
671
+
672
+ # Authlogic tries to validate the credentials passed to it. One part of
673
+ # validation is actually finding the user and making sure it exists.
674
+ # What method it uses the do this is up to you.
675
+ #
676
+ # Let's say you have a UserSession that is authenticating a User. By
677
+ # default UserSession will call User.find_by_login(login). You can
678
+ # change what method UserSession calls by specifying it here. Then in
679
+ # your User model you can make that method do anything you want, giving
680
+ # you complete control of how users are found by the UserSession.
681
+ #
682
+ # Let's take an example: You want to allow users to login by username or
683
+ # email. Set this to the name of the class method that does this in the
684
+ # User model. Let's call it "find_by_username_or_email"
685
+ #
686
+ # class User < ActiveRecord::Base
687
+ # def self.find_by_username_or_email(login)
688
+ # find_by_username(login) || find_by_email(login)
689
+ # end
690
+ # end
691
+ #
692
+ # Now just specify the name of this method for this configuration option
693
+ # and you are all set. You can do anything you want here. Maybe you
694
+ # allow users to have multiple logins and you want to search a has_many
695
+ # relationship, etc. The sky is the limit.
696
+ #
697
+ # * <tt>Default:</tt> "find_by_smart_case_login_field"
698
+ # * <tt>Accepts:</tt> Symbol or String
699
+ def find_by_login_method(value = nil)
700
+ rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
701
+ end
702
+ alias find_by_login_method= find_by_login_method
703
+
704
+ # The text used to identify credentials (username/password) combination
705
+ # when a bad login attempt occurs. When you show error messages for a
706
+ # bad login, it's considered good security practice to hide which field
707
+ # the user has entered incorrectly (the login field or the password
708
+ # field). For a full explanation, see
709
+ # http://www.gnucitizen.org/blog/username-enumeration-vulnerabilities/
710
+ #
711
+ # Example of use:
712
+ #
713
+ # class UserSession < Authlogic::Session::Base
714
+ # generalize_credentials_error_messages true
715
+ # end
716
+ #
717
+ # This would make the error message for bad logins and bad passwords
718
+ # look identical:
719
+ #
720
+ # Login/Password combination is not valid
721
+ #
722
+ # Alternatively you may use a custom message:
723
+ #
724
+ # class UserSession < AuthLogic::Session::Base
725
+ # generalize_credentials_error_messages "Your login information is invalid"
726
+ # end
727
+ #
728
+ # This will instead show your custom error message when the UserSession is invalid.
729
+ #
730
+ # The downside to enabling this is that is can be too vague for a user
731
+ # that has a hard time remembering their username and password
732
+ # combinations. It also disables the ability to to highlight the field
733
+ # with the error when you use form_for.
734
+ #
735
+ # If you are developing an app where security is an extreme priority
736
+ # (such as a financial application), then you should enable this.
737
+ # Otherwise, leaving this off is fine.
738
+ #
739
+ # * <tt>Default</tt> false
740
+ # * <tt>Accepts:</tt> Boolean
741
+ def generalize_credentials_error_messages(value = nil)
742
+ rw_config(:generalize_credentials_error_messages, value, false)
743
+ end
744
+ alias generalize_credentials_error_messages= generalize_credentials_error_messages
745
+
746
+ # HTTP authentication realm
747
+ #
748
+ # Sets the HTTP authentication realm.
749
+ #
750
+ # Note: This option has no effect unless request_http_basic_auth is true
751
+ #
752
+ # * <tt>Default:</tt> 'Application'
753
+ # * <tt>Accepts:</tt> String
754
+ def http_basic_auth_realm(value = nil)
755
+ rw_config(:http_basic_auth_realm, value, "Application")
756
+ end
757
+ alias http_basic_auth_realm= http_basic_auth_realm
758
+
759
+ # Should the cookie be set as httponly? If true, the cookie will not be
760
+ # accessible from javascript
761
+ #
762
+ # * <tt>Default:</tt> true
763
+ # * <tt>Accepts:</tt> Boolean
764
+ def httponly(value = nil)
765
+ rw_config(:httponly, value, true)
766
+ end
767
+ alias httponly= httponly
768
+
769
+ # How to name the class, works JUST LIKE ActiveRecord, except it uses
770
+ # the following namespace:
771
+ #
772
+ # authlogic.models.user_session
773
+ def human_name(*)
774
+ I18n.t("models.#{name.underscore}", count: 1, default: name.humanize)
775
+ end
776
+
777
+ def i18n_scope
778
+ I18n.scope
779
+ end
780
+
781
+ # The name of the class that this session is authenticating with. For
782
+ # example, the UserSession class will authenticate with the User class
783
+ # unless you specify otherwise in your configuration. See
784
+ # authenticate_with for information on how to change this value.
785
+ def klass
786
+ @klass ||= klass_name ? klass_name.constantize : nil
787
+ end
788
+
789
+ # The string of the model name class guessed from the actual session class name.
790
+ def klass_name
791
+ return @klass_name if defined?(@klass_name)
792
+ @klass_name = name.scan(/(.*)Session/)[0]
793
+ @klass_name = klass_name ? klass_name[0] : nil
794
+ end
795
+
796
+ # The name of the method you want Authlogic to create for storing the
797
+ # login / username. Keep in mind this is just for your
798
+ # Authlogic::Session, if you want it can be something completely
799
+ # different than the field in your model. So if you wanted people to
800
+ # login with a field called "login" and then find users by email this is
801
+ # completely doable. See the find_by_login_method configuration option
802
+ # for more details.
803
+ #
804
+ # * <tt>Default:</tt> klass.login_field || klass.email_field
805
+ # * <tt>Accepts:</tt> Symbol or String
806
+ def login_field(value = nil)
807
+ rw_config(:login_field, value, klass.login_field || klass.email_field)
808
+ end
809
+ alias login_field= login_field
810
+
811
+ # With acts_as_authentic you get a :logged_in_timeout configuration
812
+ # option. If this is set, after this amount of time has passed the user
813
+ # will be marked as logged out. Obviously, since web based apps are on a
814
+ # per request basis, we have to define a time limit threshold that
815
+ # determines when we consider a user to be "logged out". Meaning, if
816
+ # they login and then leave the website, when do mark them as logged
817
+ # out? I recommend just using this as a fun feature on your website or
818
+ # reports, giving you a ballpark number of users logged in and active.
819
+ # This is not meant to be a dead accurate representation of a user's
820
+ # logged in state, since there is really no real way to do this with web
821
+ # based apps. Think about a user that logs in and doesn't log out. There
822
+ # is no action that tells you that the user isn't technically still
823
+ # logged in and active.
824
+ #
825
+ # That being said, you can use that feature to require a new login if
826
+ # their session times out. Similar to how financial sites work. Just set
827
+ # this option to true and if your record returns true for stale? then
828
+ # they will be required to log back in.
829
+ #
830
+ # Lastly, UserSession.find will still return an object if the session is
831
+ # stale, but you will not get a record. This allows you to determine if
832
+ # the user needs to log back in because their session went stale, or
833
+ # because they just aren't logged in. Just call
834
+ # current_user_session.stale? as your flag.
835
+ #
836
+ # * <tt>Default:</tt> false
837
+ # * <tt>Accepts:</tt> Boolean
838
+ def logout_on_timeout(value = nil)
839
+ rw_config(:logout_on_timeout, value, false)
840
+ end
841
+ alias logout_on_timeout= logout_on_timeout
842
+
843
+ # Every time a session is found the last_request_at field for that record is
844
+ # updated with the current time, if that field exists. If you want to limit how
845
+ # frequent that field is updated specify the threshold here. For example, if your
846
+ # user is making a request every 5 seconds, and you feel this is too frequent, and
847
+ # feel a minute is a good threshold. Set this to 1.minute. Once a minute has
848
+ # passed in between requests the field will be updated.
849
+ #
850
+ # * <tt>Default:</tt> 0
851
+ # * <tt>Accepts:</tt> integer representing time in seconds
852
+ def last_request_at_threshold(value = nil)
853
+ rw_config(:last_request_at_threshold, value, 0)
854
+ end
855
+ alias last_request_at_threshold= last_request_at_threshold
856
+
857
+ # Works exactly like cookie_key, but for params. So a user can login via
858
+ # params just like a cookie or a session. Your URL would look like:
859
+ #
860
+ # http://www.domain.com?user_credentials=my_single_access_key
861
+ #
862
+ # You can change the "user_credentials" key above with this
863
+ # configuration option. Keep in mind, just like cookie_key, if you
864
+ # supply an id the id will be appended to the front. Check out
865
+ # cookie_key for more details. Also checkout the "Single Access /
866
+ # Private Feeds Access" section in the README.
867
+ #
868
+ # * <tt>Default:</tt> cookie_key
869
+ # * <tt>Accepts:</tt> String
870
+ def params_key(value = nil)
871
+ rw_config(:params_key, value, cookie_key)
872
+ end
873
+ alias params_key= params_key
874
+
875
+ # Works exactly like login_field, but for the password instead. Returns
876
+ # :password if a login_field exists.
877
+ #
878
+ # * <tt>Default:</tt> :password
879
+ # * <tt>Accepts:</tt> Symbol or String
880
+ def password_field(value = nil)
881
+ rw_config(:password_field, value, login_field && :password)
882
+ end
883
+ alias password_field= password_field
884
+
885
+ # Whether or not to request HTTP authentication
886
+ #
887
+ # If set to true and no HTTP authentication credentials are sent with
888
+ # the request, the Rails controller method
889
+ # authenticate_or_request_with_http_basic will be used and a '401
890
+ # Authorization Required' header will be sent with the response. In
891
+ # most cases, this will cause the classic HTTP authentication popup to
892
+ # appear in the users browser.
893
+ #
894
+ # If set to false, the Rails controller method
895
+ # authenticate_with_http_basic is used and no 401 header is sent.
896
+ #
897
+ # Note: This parameter has no effect unless allow_http_basic_auth is
898
+ # true
899
+ #
900
+ # * <tt>Default:</tt> false
901
+ # * <tt>Accepts:</tt> Boolean
902
+ def request_http_basic_auth(value = nil)
903
+ rw_config(:request_http_basic_auth, value, false)
904
+ end
905
+ alias request_http_basic_auth= request_http_basic_auth
906
+
907
+ # If sessions should be remembered by default or not.
908
+ #
909
+ # * <tt>Default:</tt> false
910
+ # * <tt>Accepts:</tt> Boolean
911
+ def remember_me(value = nil)
912
+ rw_config(:remember_me, value, false)
913
+ end
914
+ alias remember_me= remember_me
915
+
916
+ # The length of time until the cookie expires.
917
+ #
918
+ # * <tt>Default:</tt> 3.months
919
+ # * <tt>Accepts:</tt> Integer, length of time in seconds, such as 60 or 3.months
920
+ def remember_me_for(value = nil)
921
+ rw_config(:remember_me_for, value, 3.months)
922
+ end
923
+ alias remember_me_for= remember_me_for
924
+
925
+ # Should the cookie be prevented from being send along with cross-site
926
+ # requests?
927
+ #
928
+ # * <tt>Default:</tt> nil
929
+ # * <tt>Accepts:</tt> String, one of nil, 'Lax' or 'Strict'
930
+ def same_site(value = nil)
931
+ unless VALID_SAME_SITE_VALUES.include?(value)
932
+ msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
933
+ raise ArgumentError, msg
934
+ end
935
+ rw_config(:same_site, value)
936
+ end
937
+ alias same_site= same_site
938
+
939
+ # The current scope set, should be used in the block passed to with_scope.
940
+ def scope
941
+ RequestStore.store[:authlogic_scope]
942
+ end
943
+
944
+ # Should the cookie be set as secure? If true, the cookie will only be sent over
945
+ # SSL connections
946
+ #
947
+ # * <tt>Default:</tt> true
948
+ # * <tt>Accepts:</tt> Boolean
949
+ def secure(value = nil)
950
+ rw_config(:secure, value, true)
951
+ end
952
+ alias secure= secure
953
+
954
+ # Should the cookie be signed? If the controller adapter supports it, this is a
955
+ # measure against cookie tampering.
956
+ def sign_cookie(value = nil)
957
+ if value && !controller.cookies.respond_to?(:signed)
958
+ raise "Signed cookies not supported with #{controller.class}!"
959
+ end
960
+ rw_config(:sign_cookie, value, false)
961
+ end
962
+ alias sign_cookie= sign_cookie
963
+
964
+ # Works exactly like cookie_key, but for sessions. See cookie_key for more info.
965
+ #
966
+ # * <tt>Default:</tt> cookie_key
967
+ # * <tt>Accepts:</tt> Symbol or String
968
+ def session_key(value = nil)
969
+ rw_config(:session_key, value, cookie_key)
970
+ end
971
+ alias session_key= session_key
972
+
973
+ # Authentication is allowed via a single access token, but maybe this is
974
+ # something you don't want for your application as a whole. Maybe this
975
+ # is something you only want for specific request types. Specify a list
976
+ # of allowed request types and single access authentication will only be
977
+ # allowed for the ones you specify.
978
+ #
979
+ # * <tt>Default:</tt> ["application/rss+xml", "application/atom+xml"]
980
+ # * <tt>Accepts:</tt> String of a request type, or :all or :any to
981
+ # allow single access authentication for any and all request types
982
+ def single_access_allowed_request_types(value = nil)
983
+ rw_config(
984
+ :single_access_allowed_request_types,
985
+ value,
986
+ ["application/rss+xml", "application/atom+xml"]
987
+ )
988
+ end
989
+ alias single_access_allowed_request_types= single_access_allowed_request_types
990
+
991
+ # The name of the method in your model used to verify the password. This
992
+ # should be an instance method. It should also be prepared to accept a
993
+ # raw password and a crytped password.
994
+ #
995
+ # * <tt>Default:</tt> "valid_password?" defined in acts_as_authentic/password.rb
996
+ # * <tt>Accepts:</tt> Symbol or String
997
+ def verify_password_method(value = nil)
998
+ rw_config(:verify_password_method, value, "valid_password?")
999
+ end
1000
+ alias verify_password_method= verify_password_method
1001
+
1002
+ # What with_scopes focuses on is scoping the query when finding the
1003
+ # object and the name of the cookie / session. It works very similar to
1004
+ # ActiveRecord::Base#with_scopes. It accepts a hash with any of the
1005
+ # following options:
1006
+ #
1007
+ # * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find.
1008
+ # This is used when trying to find the record.
1009
+ # * <tt>id:</tt> The id of the session, this gets merged with the real id. For
1010
+ # information ids see the id method.
1011
+ #
1012
+ # Here is how you use it:
1013
+ #
1014
+ # ```
1015
+ # UserSession.with_scope(find_options: User.where(account_id: 2), id: "account_2") do
1016
+ # UserSession.find
1017
+ # end
1018
+ # ```
1019
+ #
1020
+ # Essentially what the above does is scope the searching of the object
1021
+ # with the sql you provided. So instead of:
1022
+ #
1023
+ # ```
1024
+ # User.where("login = 'ben'").first
1025
+ # ```
1026
+ #
1027
+ # it would effectively be:
1028
+ #
1029
+ # ```
1030
+ # User.where("login = 'ben' and account_id = 2").first
1031
+ # ```
1032
+ #
1033
+ # You will also notice the :id option. This works just like the id
1034
+ # method. It scopes your cookies. So the name of your cookie will be:
1035
+ #
1036
+ # account_2_user_credentials
1037
+ #
1038
+ # instead of:
1039
+ #
1040
+ # user_credentials
1041
+ #
1042
+ # What is also nifty about scoping with an :id is that it merges your
1043
+ # id's. So if you do:
1044
+ #
1045
+ # UserSession.with_scope(
1046
+ # find_options: { conditions: "account_id = 2"},
1047
+ # id: "account_2"
1048
+ # ) do
1049
+ # session = UserSession.new
1050
+ # session.id = :secure
1051
+ # end
1052
+ #
1053
+ # The name of your cookies will be:
1054
+ #
1055
+ # secure_account_2_user_credentials
1056
+ def with_scope(options = {})
1057
+ raise ArgumentError, "You must provide a block" unless block_given?
1058
+ self.scope = options
1059
+ result = yield
1060
+ self.scope = nil
1061
+ result
1062
+ end
1063
+ end
1064
+
1065
+ # Constructor
1066
+ # ===========
1067
+
1068
+ # rubocop:disable Metrics/AbcSize
1069
+ def initialize(*args)
1070
+ @id = nil
1071
+ self.scope = self.class.scope
1072
+
1073
+ # Creating an alias method for the "record" method based on the klass
1074
+ # name, so that we can do:
1075
+ #
1076
+ # session.user
1077
+ #
1078
+ # instead of:
1079
+ #
1080
+ # session.record
1081
+ unless self.class.configured_klass_methods
1082
+ self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
1083
+ self.class.configured_klass_methods = true
1084
+ end
1085
+
1086
+ raise Activation::NotActivatedError unless self.class.activated?
1087
+ unless self.class.configured_password_methods
1088
+ configure_password_methods
1089
+ self.class.configured_password_methods = true
1090
+ end
1091
+ instance_variable_set("@#{password_field}", nil)
1092
+ self.credentials = args
1093
+ end
1094
+ # rubocop:enable Metrics/AbcSize
1095
+
1096
+ # Public instance methods
1097
+ # =======================
1098
+
1099
+ # You should use this as a place holder for any records that you find
1100
+ # during validation. The main reason for this is to allow other modules to
1101
+ # use it if needed. Take the failed_login_count feature, it needs this in
1102
+ # order to increase the failed login count.
1103
+ def attempted_record
1104
+ @attempted_record
1105
+ end
1106
+
1107
+ # See attempted_record
1108
+ def attempted_record=(value)
1109
+ value = priority_record if value == priority_record # See notes in `.find`
1110
+ @attempted_record = value
1111
+ end
1112
+
1113
+ # Returns true when the consecutive_failed_logins_limit has been
1114
+ # exceeded and is being temporarily banned. Notice the word temporary,
1115
+ # the user will not be permanently banned unless you choose to do so
1116
+ # with configuration. By default they will be banned for 2 hours. During
1117
+ # that 2 hour period this method will return true.
1118
+ def being_brute_force_protected?
1119
+ exceeded_failed_logins_limit? &&
1120
+ (
1121
+ failed_login_ban_for <= 0 ||
1122
+ attempted_record.respond_to?(:updated_at) &&
1123
+ attempted_record.updated_at >= failed_login_ban_for.seconds.ago
1124
+ )
1125
+ end
1126
+
1127
+ # The credentials you passed to create your session, in a redacted format
1128
+ # intended for output (debugging, logging). See credentials= for more
1129
+ # info.
1130
+ #
1131
+ # @api private
1132
+ def credentials
1133
+ if authenticating_with_unauthorized_record?
1134
+ { unauthorized_record: "<protected>" }
1135
+ elsif authenticating_with_password?
1136
+ {
1137
+ login_field.to_sym => send(login_field),
1138
+ password_field.to_sym => "<protected>"
1139
+ }
1140
+ else
1141
+ {}
1142
+ end
1143
+ end
1144
+
1145
+ # Set your credentials before you save your session. There are many
1146
+ # method signatures.
1147
+ #
1148
+ # ```
1149
+ # # A hash of credentials is most common
1150
+ # session.credentials = { login: "foo", password: "bar", remember_me: true }
1151
+ #
1152
+ # # You must pass an actual Hash, `ActionController::Parameters` is
1153
+ # # specifically not allowed.
1154
+ #
1155
+ # # You can pass an array of objects:
1156
+ # session.credentials = [my_user_object, true]
1157
+ #
1158
+ # # If you need to set an id (see `#id`) pass it last.
1159
+ # session.credentials = [
1160
+ # {:login => "foo", :password => "bar", :remember_me => true},
1161
+ # :my_id
1162
+ # ]
1163
+ # session.credentials = [my_user_object, true, :my_id]
1164
+ #
1165
+ # The `id` is something that you control yourself, it should never be
1166
+ # set from a hash or a form.
1167
+ #
1168
+ # # Finally, there's priority_record
1169
+ # [{ priority_record: my_object }, :my_id]
1170
+ # ```
1171
+ #
1172
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
1173
+ def credentials=(value)
1174
+ normalized = Array.wrap(value)
1175
+ if normalized.first.class.name == "ActionController::Parameters"
1176
+ raise TypeError, E_AC_PARAMETERS
1177
+ end
1178
+
1179
+ # Allows you to set the remember_me option when passing credentials.
1180
+ values = value.is_a?(Array) ? value : [value]
1181
+ case values.first
1182
+ when Hash
1183
+ if values.first.with_indifferent_access.key?(:remember_me)
1184
+ self.remember_me = values.first.with_indifferent_access[:remember_me]
1185
+ end
1186
+ else
1187
+ r = values.find { |val| val.is_a?(TrueClass) || val.is_a?(FalseClass) }
1188
+ self.remember_me = r unless r.nil?
1189
+ end
1190
+
1191
+ # Accepts the login_field / password_field credentials combination in
1192
+ # hash form.
1193
+ #
1194
+ # You must pass an actual Hash, `ActionController::Parameters` is
1195
+ # specifically not allowed.
1196
+ values = Array.wrap(value)
1197
+ if values.first.is_a?(Hash)
1198
+ sliced = values
1199
+ .first
1200
+ .with_indifferent_access
1201
+ .slice(login_field, password_field)
1202
+ sliced.each do |field, val|
1203
+ next if val.blank?
1204
+ send("#{field}=", val)
1205
+ end
1206
+ end
1207
+
1208
+ # Setting the unauthorized record if it exists in the credentials passed.
1209
+ values = value.is_a?(Array) ? value : [value]
1210
+ self.unauthorized_record = values.first if values.first.class < ::ActiveRecord::Base
1211
+
1212
+ # Setting the id if it is passed in the credentials.
1213
+ values = value.is_a?(Array) ? value : [value]
1214
+ self.id = values.last if values.last.is_a?(Symbol)
1215
+
1216
+ # Setting priority record if it is passed. The only way it can be passed
1217
+ # is through an array:
1218
+ #
1219
+ # session.credentials = [real_user_object, priority_user_object]
1220
+ #
1221
+ # See notes in `.find`
1222
+ values = value.is_a?(Array) ? value : [value]
1223
+ self.priority_record = values[1] if values[1].class < ::ActiveRecord::Base
1224
+ end
1225
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
1226
+
1227
+ # Clears all errors and the associated record, you should call this
1228
+ # terminate a session, thus requiring the user to authenticate again if
1229
+ # it is needed.
1230
+ def destroy
1231
+ run_callbacks :before_destroy
1232
+ save_record
1233
+ errors.clear
1234
+ @record = nil
1235
+ run_callbacks :after_destroy
1236
+ true
1237
+ end
1238
+
1239
+ def destroyed?
1240
+ record.nil?
1241
+ end
1242
+
1243
+ # @api public
1244
+ def errors
1245
+ @errors ||= ::ActiveModel::Errors.new(self)
1246
+ end
1247
+
1248
+ # If the cookie should be marked as httponly (not accessible via javascript)
1249
+ def httponly
1250
+ return @httponly if defined?(@httponly)
1251
+ @httponly = self.class.httponly
1252
+ end
1253
+
1254
+ # Accepts a boolean as to whether the cookie should be marked as
1255
+ # httponly. If true, the cookie will not be accessible from javascript
1256
+ def httponly=(value)
1257
+ @httponly = value
1258
+ end
1259
+
1260
+ # See httponly
1261
+ def httponly?
1262
+ httponly == true || httponly == "true" || httponly == "1"
1263
+ end
1264
+
1265
+ # Allows you to set a unique identifier for your session, so that you can
1266
+ # have more than 1 session at a time.
1267
+ #
1268
+ # For example, you may want to have simultaneous private and public
1269
+ # sessions. Or, a normal user session and a "secure" user session. The
1270
+ # secure user session would be created only when they want to modify their
1271
+ # billing information, or other sensitive information.
1272
+ #
1273
+ # You can set the id during initialization (see initialize for more
1274
+ # information), or as an attribute:
1275
+ #
1276
+ # session.id = :my_id
1277
+ #
1278
+ # Set your id before you save your session.
1279
+ #
1280
+ # Lastly, to retrieve your session with the id, use the `.find` method.
1281
+ def id
1282
+ @id
1283
+ end
1284
+
1285
+ def inspect
1286
+ format(
1287
+ "#<%s: %s>",
1288
+ self.class.name,
1289
+ credentials.blank? ? "no credentials provided" : credentials.inspect
1290
+ )
1291
+ end
1292
+
1293
+ def invalid_password?
1294
+ invalid_password == true
1295
+ end
1296
+
1297
+ # Don't use this yourself, this is to just trick some of the helpers
1298
+ # since this is the method it calls.
1299
+ def new_record?
1300
+ new_session?
1301
+ end
1302
+
1303
+ # Returns true if the session is new, meaning no action has been taken
1304
+ # on it and a successful save has not taken place.
1305
+ def new_session?
1306
+ new_session != false
1307
+ end
1308
+
1309
+ def persisted?
1310
+ !(new_record? || destroyed?)
1311
+ end
1312
+
1313
+ # Returns boolean indicating if the session is being persisted or not,
1314
+ # meaning the user does not have to explicitly log in in order to be
1315
+ # logged in.
1316
+ #
1317
+ # If the session has no associated record, it will try to find a record
1318
+ # and persist the session.
1319
+ #
1320
+ # This is the method that the class level method find uses to ultimately
1321
+ # persist the session.
1322
+ def persisting?
1323
+ return true unless record.nil?
1324
+ self.attempted_record = nil
1325
+ self.remember_me = cookie_credentials&.remember_me?
1326
+ run_callbacks :before_persisting
1327
+ run_callbacks :persist
1328
+ ensure_authentication_attempted
1329
+ if errors.empty? && !attempted_record.nil?
1330
+ self.record = attempted_record
1331
+ run_callbacks :after_persisting
1332
+ save_record
1333
+ self.new_session = false
1334
+ true
1335
+ else
1336
+ false
1337
+ end
1338
+ end
1339
+
1340
+ def save_record(alternate_record = nil)
1341
+ r = alternate_record || record
1342
+ if r != priority_record
1343
+ if r&.has_changes_to_save? && !r.readonly?
1344
+ r.save_without_session_maintenance(validate: false)
1345
+ end
1346
+ end
1347
+ end
1348
+
1349
+ # Tells you if the record is stale or not. Meaning the record has timed
1350
+ # out. This will only return true if you set logout_on_timeout to true
1351
+ # in your configuration. Basically how a bank website works. If you
1352
+ # aren't active over a certain period of time your session becomes stale
1353
+ # and requires you to log back in.
1354
+ def stale?
1355
+ if remember_me?
1356
+ remember_me_expired?
1357
+ else
1358
+ !stale_record.nil? || (logout_on_timeout? && record && record.logged_out?)
1359
+ end
1360
+ end
1361
+
1362
+ # Is the cookie going to expire after the session is over, or will it stick around?
1363
+ def remember_me
1364
+ return @remember_me if defined?(@remember_me)
1365
+ @remember_me = self.class.remember_me
1366
+ end
1367
+
1368
+ # Accepts a boolean as a flag to remember the session or not. Basically
1369
+ # to expire the cookie at the end of the session or keep it for
1370
+ # "remember_me_until".
1371
+ def remember_me=(value)
1372
+ @remember_me = value
1373
+ end
1374
+
1375
+ # See remember_me
1376
+ def remember_me?
1377
+ remember_me == true || remember_me == "true" || remember_me == "1"
1378
+ end
1379
+
1380
+ # Has the cookie expired due to current time being greater than remember_me_until.
1381
+ def remember_me_expired?
1382
+ return unless remember_me?
1383
+ cookie_credentials.remember_me_until < ::Time.now
1384
+ end
1385
+
1386
+ # How long to remember the user if remember_me is true. This is based on the class
1387
+ # level configuration: remember_me_for
1388
+ def remember_me_for
1389
+ return unless remember_me?
1390
+ self.class.remember_me_for
1391
+ end
1392
+
1393
+ # When to expire the cookie. See remember_me_for configuration option to change
1394
+ # this.
1395
+ def remember_me_until
1396
+ return unless remember_me?
1397
+ remember_me_for.from_now
1398
+ end
1399
+
1400
+ # After you have specified all of the details for your session you can
1401
+ # try to save it. This will run validation checks and find the
1402
+ # associated record, if all validation passes. If validation does not
1403
+ # pass, the save will fail and the errors will be stored in the errors
1404
+ # object.
1405
+ def save
1406
+ result = nil
1407
+ if valid?
1408
+ self.record = attempted_record
1409
+
1410
+ run_callbacks :before_save
1411
+ run_callbacks(new_session? ? :before_create : :before_update)
1412
+ run_callbacks(new_session? ? :after_create : :after_update)
1413
+ run_callbacks :after_save
1414
+
1415
+ save_record
1416
+ self.new_session = false
1417
+ result = true
1418
+ else
1419
+ result = false
1420
+ end
1421
+
1422
+ yield result if block_given?
1423
+ result
1424
+ end
1425
+
1426
+ # Same as save but raises an exception of validation errors when
1427
+ # validation fails
1428
+ def save!
1429
+ result = save
1430
+ raise Existence::SessionInvalidError, self unless result
1431
+ result
1432
+ end
1433
+
1434
+ # If the cookie should be marked as secure (SSL only)
1435
+ def secure
1436
+ return @secure if defined?(@secure)
1437
+ @secure = self.class.secure
1438
+ end
1439
+
1440
+ # Accepts a boolean as to whether the cookie should be marked as secure. If true
1441
+ # the cookie will only ever be sent over an SSL connection.
1442
+ def secure=(value)
1443
+ @secure = value
1444
+ end
1445
+
1446
+ # See secure
1447
+ def secure?
1448
+ secure == true || secure == "true" || secure == "1"
1449
+ end
1450
+
1451
+ # If the cookie should be marked as SameSite with 'Lax' or 'Strict' flag.
1452
+ def same_site
1453
+ return @same_site if defined?(@same_site)
1454
+ @same_site = self.class.same_site(nil)
1455
+ end
1456
+
1457
+ # Accepts nil, 'Lax' or 'Strict' as possible flags.
1458
+ def same_site=(value)
1459
+ unless VALID_SAME_SITE_VALUES.include?(value)
1460
+ msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
1461
+ raise ArgumentError, msg
1462
+ end
1463
+ @same_site = value
1464
+ end
1465
+
1466
+ # If the cookie should be signed
1467
+ def sign_cookie
1468
+ return @sign_cookie if defined?(@sign_cookie)
1469
+ @sign_cookie = self.class.sign_cookie
1470
+ end
1471
+
1472
+ # Accepts a boolean as to whether the cookie should be signed. If true
1473
+ # the cookie will be saved and verified using a signature.
1474
+ def sign_cookie=(value)
1475
+ @sign_cookie = value
1476
+ end
1477
+
1478
+ # See sign_cookie
1479
+ def sign_cookie?
1480
+ sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
1481
+ end
1482
+
1483
+ # The scope of the current object
1484
+ def scope
1485
+ @scope ||= {}
1486
+ end
1487
+
1488
+ def to_key
1489
+ new_record? ? nil : record.to_key
1490
+ end
1491
+
1492
+ # For rails >= 3.0
1493
+ def to_model
1494
+ self
1495
+ end
1496
+
1497
+ # Determines if the information you provided for authentication is valid
1498
+ # or not. If there is a problem with the information provided errors will
1499
+ # be added to the errors object and this method will return false.
1500
+ def valid?
1501
+ errors.clear
1502
+ self.attempted_record = nil
1503
+
1504
+ run_callbacks(:before_validation)
1505
+ run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
1506
+
1507
+ # Run the `validate` callbacks, eg. `validate_by_password`.
1508
+ # This is when `attempted_record` is set.
1509
+ run_callbacks(:validate)
1510
+
1511
+ ensure_authentication_attempted
1512
+
1513
+ if errors.empty?
1514
+ run_callbacks(new_session? ? :after_validation_on_create : :after_validation_on_update)
1515
+ run_callbacks(:after_validation)
1516
+ end
1517
+
1518
+ save_record(attempted_record)
1519
+ errors.empty?
1520
+ end
1521
+
1522
+ # Private class methods
1523
+ # =====================
1524
+
1525
+ class << self
1526
+ private
1527
+
1528
+ def scope=(value)
1529
+ RequestStore.store[:authlogic_scope] = value
1530
+ end
1531
+ end
1532
+
1533
+ # Private instance methods
1534
+ # ========================
1535
+
1536
+ private
1537
+
1538
+ def add_general_credentials_error
1539
+ error_message =
1540
+ if self.class.generalize_credentials_error_messages.is_a? String
1541
+ self.class.generalize_credentials_error_messages
1542
+ else
1543
+ "#{login_field.to_s.humanize}/Password combination is not valid"
1544
+ end
1545
+ errors.add(
1546
+ :base,
1547
+ I18n.t("error_messages.general_credentials_error", default: error_message)
1548
+ )
1549
+ end
1550
+
1551
+ def add_invalid_password_error
1552
+ if generalize_credentials_error_messages?
1553
+ add_general_credentials_error
1554
+ else
1555
+ errors.add(
1556
+ password_field,
1557
+ I18n.t("error_messages.password_invalid", default: "is not valid")
1558
+ )
1559
+ end
1560
+ end
1561
+
1562
+ def add_login_not_found_error
1563
+ if generalize_credentials_error_messages?
1564
+ add_general_credentials_error
1565
+ else
1566
+ errors.add(
1567
+ login_field,
1568
+ I18n.t("error_messages.login_not_found", default: "is not valid")
1569
+ )
1570
+ end
1571
+ end
1572
+
1573
+ def allow_http_basic_auth?
1574
+ self.class.allow_http_basic_auth == true
1575
+ end
1576
+
1577
+ def authenticating_with_password?
1578
+ login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
1579
+ end
1580
+
1581
+ def authenticating_with_unauthorized_record?
1582
+ !unauthorized_record.nil?
1583
+ end
1584
+
1585
+ # Used for things like cookie_key, session_key, etc.
1586
+ # Examples:
1587
+ # - user_credentials
1588
+ # - ziggity_zack_user_credentials
1589
+ # - ziggity_zack is an "id"
1590
+ # - see persistence_token_test.rb
1591
+ def build_key(last_part)
1592
+ [id, scope[:id], last_part].compact.join("_")
1593
+ end
1594
+
1595
+ def clear_failed_login_count
1596
+ if record.respond_to?(:failed_login_count)
1597
+ record.failed_login_count = 0
1598
+ end
1599
+ end
1600
+
1601
+ def consecutive_failed_logins_limit
1602
+ self.class.consecutive_failed_logins_limit
1603
+ end
1604
+
1605
+ def controller
1606
+ self.class.controller
1607
+ end
1608
+
1609
+ def cookie_key
1610
+ build_key(self.class.cookie_key)
1611
+ end
1612
+
1613
+ # Look in the `cookie_jar`, find the cookie that contains authlogic
1614
+ # credentials (`cookie_key`).
1615
+ #
1616
+ # @api private
1617
+ # @return ::Authlogic::CookieCredentials or if no cookie is found, nil
1618
+ def cookie_credentials
1619
+ cookie_value = cookie_jar[cookie_key]
1620
+ unless cookie_value.nil?
1621
+ ::Authlogic::CookieCredentials.parse(cookie_value)
1622
+ end
1623
+ end
1624
+
1625
+ def cookie_jar
1626
+ if self.class.sign_cookie
1627
+ controller.cookies.signed
1628
+ else
1629
+ controller.cookies
1630
+ end
1631
+ end
1632
+
1633
+ def configure_password_methods
1634
+ define_login_field_methods
1635
+ define_password_field_methods
1636
+ end
1637
+
1638
+ def define_login_field_methods
1639
+ return unless login_field
1640
+ self.class.send(:attr_writer, login_field) unless respond_to?("#{login_field}=")
1641
+ self.class.send(:attr_reader, login_field) unless respond_to?(login_field)
1642
+ end
1643
+
1644
+ def define_password_field_methods
1645
+ return unless password_field
1646
+ self.class.send(:attr_writer, password_field) unless respond_to?("#{password_field}=")
1647
+ self.class.send(:define_method, password_field) {} unless respond_to?(password_field)
1648
+
1649
+ # The password should not be accessible publicly. This way forms
1650
+ # using form_for don't fill the password with the attempted
1651
+ # password. To prevent this we just create this method that is
1652
+ # private.
1653
+ self.class.class_eval(
1654
+ <<-EOS, __FILE__, __LINE__ + 1
1655
+ private
1656
+ def protected_#{password_field}
1657
+ @#{password_field}
1658
+ end
1659
+ EOS
1660
+ )
1661
+ end
1662
+
1663
+ def destroy_cookie
1664
+ controller.cookies.delete cookie_key, domain: controller.cookie_domain
1665
+ end
1666
+
1667
+ def disable_magic_states?
1668
+ self.class.disable_magic_states == true
1669
+ end
1670
+
1671
+ def enforce_timeout
1672
+ if stale?
1673
+ self.stale_record = record
1674
+ self.record = nil
1675
+ end
1676
+ end
1677
+
1678
+ def ensure_authentication_attempted
1679
+ if errors.empty? && attempted_record.nil?
1680
+ errors.add(
1681
+ :base,
1682
+ I18n.t(
1683
+ "error_messages.no_authentication_details",
1684
+ default: "You did not provide any details for authentication."
1685
+ )
1686
+ )
1687
+ end
1688
+ end
1689
+
1690
+ def exceeded_failed_logins_limit?
1691
+ !attempted_record.nil? &&
1692
+ attempted_record.respond_to?(:failed_login_count) &&
1693
+ consecutive_failed_logins_limit > 0 &&
1694
+ attempted_record.failed_login_count &&
1695
+ attempted_record.failed_login_count >= consecutive_failed_logins_limit
1696
+ end
1697
+
1698
+ def find_by_login_method
1699
+ self.class.find_by_login_method
1700
+ end
1701
+
1702
+ def generalize_credentials_error_messages?
1703
+ self.class.generalize_credentials_error_messages
1704
+ end
1705
+
1706
+ # @api private
1707
+ def generate_cookie_for_saving
1708
+ creds = ::Authlogic::CookieCredentials.new(
1709
+ record.persistence_token,
1710
+ record.send(record.class.primary_key),
1711
+ remember_me? ? remember_me_until : nil
1712
+ )
1713
+ {
1714
+ value: creds.to_s,
1715
+ expires: remember_me_until,
1716
+ secure: secure,
1717
+ httponly: httponly,
1718
+ same_site: same_site,
1719
+ domain: controller.cookie_domain
1720
+ }
1721
+ end
1722
+
1723
+ # Returns a Proc to be executed by
1724
+ # `ActionController::HttpAuthentication::Basic` when credentials are
1725
+ # present in the HTTP request.
1726
+ #
1727
+ # @api private
1728
+ # @return Proc
1729
+ def http_auth_login_proc
1730
+ proc do |login, password|
1731
+ if !login.blank? && !password.blank?
1732
+ send("#{login_field}=", login)
1733
+ send("#{password_field}=", password)
1734
+ valid?
1735
+ end
1736
+ end
1737
+ end
1738
+
1739
+ def failed_login_ban_for
1740
+ self.class.failed_login_ban_for
1741
+ end
1742
+
1743
+ def increase_failed_login_count
1744
+ if invalid_password? && attempted_record.respond_to?(:failed_login_count)
1745
+ attempted_record.failed_login_count ||= 0
1746
+ attempted_record.failed_login_count += 1
1747
+ end
1748
+ end
1749
+
1750
+ def increment_login_cout
1751
+ if record.respond_to?(:login_count)
1752
+ record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1)
1753
+ end
1754
+ end
1755
+
1756
+ def klass
1757
+ self.class.klass
1758
+ end
1759
+
1760
+ def klass_name
1761
+ self.class.klass_name
1762
+ end
1763
+
1764
+ def last_request_at_threshold
1765
+ self.class.last_request_at_threshold
1766
+ end
1767
+
1768
+ def login_field
1769
+ self.class.login_field
1770
+ end
1771
+
1772
+ def logout_on_timeout?
1773
+ self.class.logout_on_timeout == true
1774
+ end
1775
+
1776
+ def params_credentials
1777
+ controller.params[params_key]
1778
+ end
1779
+
1780
+ def params_enabled?
1781
+ if !params_credentials || !klass.column_names.include?("single_access_token")
1782
+ return false
1783
+ end
1784
+ if controller.responds_to_single_access_allowed?
1785
+ return controller.single_access_allowed?
1786
+ end
1787
+ params_enabled_by_allowed_request_types?
1788
+ end
1789
+
1790
+ def params_enabled_by_allowed_request_types?
1791
+ case single_access_allowed_request_types
1792
+ when Array
1793
+ single_access_allowed_request_types.include?(controller.request_content_type) ||
1794
+ single_access_allowed_request_types.include?(:all)
1795
+ else
1796
+ %i[all any].include?(single_access_allowed_request_types)
1797
+ end
1798
+ end
1799
+
1800
+ def params_key
1801
+ build_key(self.class.params_key)
1802
+ end
1803
+
1804
+ def password_field
1805
+ self.class.password_field
1806
+ end
1807
+
1808
+ # Tries to validate the session from information in the cookie
1809
+ def persist_by_cookie
1810
+ creds = cookie_credentials
1811
+ if creds&.persistence_token.present?
1812
+ record = search_for_record("find_by_#{klass.primary_key}", creds.record_id)
1813
+ if record && record.persistence_token == creds.persistence_token
1814
+ self.unauthorized_record = record
1815
+ end
1816
+ valid?
1817
+ else
1818
+ false
1819
+ end
1820
+ end
1821
+
1822
+ def persist_by_params
1823
+ return false unless params_enabled?
1824
+ self.unauthorized_record = search_for_record(
1825
+ "find_by_single_access_token",
1826
+ params_credentials
1827
+ )
1828
+ self.single_access = valid?
1829
+ end
1830
+
1831
+ def persist_by_http_auth
1832
+ login_proc = http_auth_login_proc
1833
+
1834
+ if self.class.request_http_basic_auth
1835
+ controller.authenticate_or_request_with_http_basic(
1836
+ self.class.http_basic_auth_realm,
1837
+ &login_proc
1838
+ )
1839
+ else
1840
+ controller.authenticate_with_http_basic(&login_proc)
1841
+ end
1842
+
1843
+ false
1844
+ end
1845
+
1846
+ def persist_by_http_auth?
1847
+ allow_http_basic_auth? && login_field && password_field
1848
+ end
1849
+
1850
+ # Tries to validate the session from information in the session
1851
+ def persist_by_session
1852
+ persistence_token, record_id = session_credentials
1853
+ if !persistence_token.nil?
1854
+ record = persist_by_session_search(persistence_token, record_id)
1855
+ if record && record.persistence_token == persistence_token
1856
+ self.unauthorized_record = record
1857
+ end
1858
+ valid?
1859
+ else
1860
+ false
1861
+ end
1862
+ end
1863
+
1864
+ # Allow finding by persistence token, because when records are created
1865
+ # the session is maintained in a before_save, when there is no id.
1866
+ # This is done for performance reasons and to save on queries.
1867
+ def persist_by_session_search(persistence_token, record_id)
1868
+ if record_id.nil?
1869
+ search_for_record("find_by_persistence_token", persistence_token.to_s)
1870
+ else
1871
+ search_for_record("find_by_#{klass.primary_key}", record_id.to_s)
1872
+ end
1873
+ end
1874
+
1875
+ def reset_stale_state
1876
+ self.stale_record = nil
1877
+ end
1878
+
1879
+ def reset_perishable_token!
1880
+ if record.respond_to?(:reset_perishable_token) &&
1881
+ !record.disable_perishable_token_maintenance?
1882
+ record.reset_perishable_token
1883
+ end
1884
+ end
1885
+
1886
+ # @api private
1887
+ def required_magic_states_for(record)
1888
+ %i[active approved confirmed].select { |state|
1889
+ record.respond_to?("#{state}?")
1890
+ }
1891
+ end
1892
+
1893
+ def reset_failed_login_count?
1894
+ exceeded_failed_logins_limit? && !being_brute_force_protected?
1895
+ end
1896
+
1897
+ def reset_failed_login_count
1898
+ attempted_record.failed_login_count = 0
1899
+ end
1900
+
1901
+ # `args[0]` is the name of a model method, like
1902
+ # `find_by_single_access_token` or `find_by_smart_case_login_field`.
1903
+ def search_for_record(*args)
1904
+ search_scope.scoping do
1905
+ klass.send(*args)
1906
+ end
1907
+ end
1908
+
1909
+ # Returns an AR relation representing the scope of the search. The
1910
+ # relation is either provided directly by, or defined by
1911
+ # `find_options`.
1912
+ def search_scope
1913
+ if scope[:find_options].is_a?(ActiveRecord::Relation)
1914
+ scope[:find_options]
1915
+ else
1916
+ conditions = scope[:find_options] && scope[:find_options][:conditions] || {}
1917
+ klass.send(:where, conditions)
1918
+ end
1919
+ end
1920
+
1921
+ # @api private
1922
+ def set_last_request_at
1923
+ current_time = klass.default_timezone == :utc ? Time.now.utc : Time.now
1924
+ MagicColumn::AssignsLastRequestAt
1925
+ .new(current_time, record, controller, last_request_at_threshold)
1926
+ .assign
1927
+ end
1928
+
1929
+ def single_access?
1930
+ single_access == true
1931
+ end
1932
+
1933
+ def single_access_allowed_request_types
1934
+ self.class.single_access_allowed_request_types
1935
+ end
1936
+
1937
+ def save_cookie
1938
+ if sign_cookie?
1939
+ controller.cookies.signed[cookie_key] = generate_cookie_for_saving
1940
+ else
1941
+ controller.cookies[cookie_key] = generate_cookie_for_saving
1942
+ end
1943
+ end
1944
+
1945
+ # @api private
1946
+ # @return [String] - Examples:
1947
+ # - user_credentials_id
1948
+ # - ziggity_zack_user_credentials_id
1949
+ # - ziggity_zack is an "id", see `#id`
1950
+ # - see persistence_token_test.rb
1951
+ def session_compound_key
1952
+ "#{session_key}_#{klass.primary_key}"
1953
+ end
1954
+
1955
+ def session_credentials
1956
+ [
1957
+ controller.session[session_key],
1958
+ controller.session[session_compound_key]
1959
+ ].collect { |i| i.nil? ? i : i.to_s }.compact
1960
+ end
1961
+
1962
+ # @return [String] - Examples:
1963
+ # - user_credentials
1964
+ # - ziggity_zack_user_credentials
1965
+ # - ziggity_zack is an "id", see `#id`
1966
+ # - see persistence_token_test.rb
1967
+ def session_key
1968
+ build_key(self.class.session_key)
1969
+ end
1970
+
1971
+ def update_info
1972
+ increment_login_cout
1973
+ clear_failed_login_count
1974
+ update_login_timestamps
1975
+ update_login_ip_addresses
1976
+ end
1977
+
1978
+ def update_login_ip_addresses
1979
+ if record.respond_to?(:current_login_ip)
1980
+ record.last_login_ip = record.current_login_ip if record.respond_to?(:last_login_ip)
1981
+ record.current_login_ip = controller.request.ip
1982
+ end
1983
+ end
1984
+
1985
+ def update_login_timestamps
1986
+ if record.respond_to?(:current_login_at)
1987
+ record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
1988
+ record.current_login_at = klass.default_timezone == :utc ? Time.now.utc : Time.now
1989
+ end
1990
+ end
1991
+
1992
+ def update_session
1993
+ update_session_set_persistence_token
1994
+ update_session_set_primary_key
1995
+ end
1996
+
1997
+ # Updates the session, setting the primary key (usually `id`) of the
1998
+ # record.
1999
+ #
2000
+ # @api private
2001
+ def update_session_set_primary_key
2002
+ compound_key = session_compound_key
2003
+ controller.session[compound_key] = record && record.send(record.class.primary_key)
2004
+ end
2005
+
2006
+ # Updates the session, setting the `persistence_token` of the record.
2007
+ #
2008
+ # @api private
2009
+ def update_session_set_persistence_token
2010
+ controller.session[session_key] = record && record.persistence_token
2011
+ end
2012
+
2013
+ # In keeping with the metaphor of ActiveRecord, verification of the
2014
+ # password is referred to as a "validation".
2015
+ def validate_by_password
2016
+ self.invalid_password = false
2017
+ validate_by_password__blank_fields
2018
+ return if errors.count > 0
2019
+ self.attempted_record = search_for_record(find_by_login_method, send(login_field))
2020
+ if attempted_record.blank?
2021
+ add_login_not_found_error
2022
+ return
2023
+ end
2024
+ validate_by_password__invalid_password
2025
+ end
2026
+
2027
+ def validate_by_password__blank_fields
2028
+ if send(login_field).blank?
2029
+ errors.add(
2030
+ login_field,
2031
+ I18n.t("error_messages.login_blank", default: "cannot be blank")
2032
+ )
2033
+ end
2034
+ if send("protected_#{password_field}").blank?
2035
+ errors.add(
2036
+ password_field,
2037
+ I18n.t("error_messages.password_blank", default: "cannot be blank")
2038
+ )
2039
+ end
2040
+ end
2041
+
2042
+ # Verify the password, usually using `valid_password?` in
2043
+ # `acts_as_authentic/password.rb`. If it cannot be verified, we
2044
+ # refer to it as "invalid".
2045
+ def validate_by_password__invalid_password
2046
+ unless attempted_record.send(
2047
+ verify_password_method,
2048
+ send("protected_#{password_field}")
2049
+ )
2050
+ self.invalid_password = true
2051
+ add_invalid_password_error
2052
+ end
2053
+ end
2054
+
2055
+ def validate_by_unauthorized_record
2056
+ self.attempted_record = unauthorized_record
2057
+ end
2058
+
2059
+ def validate_magic_states
2060
+ return true if attempted_record.nil?
2061
+ required_magic_states_for(attempted_record).each do |required_status|
2062
+ next if attempted_record.send("#{required_status}?")
2063
+ errors.add(
2064
+ :base,
2065
+ I18n.t(
2066
+ "error_messages.not_#{required_status}",
2067
+ default: "Your account is not #{required_status}"
2068
+ )
2069
+ )
2070
+ return false
2071
+ end
2072
+ true
2073
+ end
2074
+
2075
+ def validate_failed_logins
2076
+ # Clear all other error messages, as they are irrelevant at this point and can
2077
+ # only provide additional information that is not needed
2078
+ errors.clear
2079
+ duration = failed_login_ban_for == 0 ? "" : " temporarily"
2080
+ errors.add(
2081
+ :base,
2082
+ I18n.t(
2083
+ "error_messages.consecutive_failed_logins_limit_exceeded",
2084
+ default: format(
2085
+ "Consecutive failed logins limit exceeded, account has been%s disabled.",
2086
+ duration
2087
+ )
2088
+ )
2089
+ )
2090
+ end
2091
+
2092
+ def verify_password_method
2093
+ self.class.verify_password_method
2094
+ end
47
2095
  end
48
2096
  end
49
2097
  end