authlogic-nicho 6.5

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/authlogic/acts_as_authentic/base.rb +116 -0
  3. data/lib/authlogic/acts_as_authentic/email.rb +30 -0
  4. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +85 -0
  5. data/lib/authlogic/acts_as_authentic/login.rb +63 -0
  6. data/lib/authlogic/acts_as_authentic/magic_columns.rb +38 -0
  7. data/lib/authlogic/acts_as_authentic/password.rb +357 -0
  8. data/lib/authlogic/acts_as_authentic/perishable_token.rb +122 -0
  9. data/lib/authlogic/acts_as_authentic/persistence_token.rb +70 -0
  10. data/lib/authlogic/acts_as_authentic/queries/case_sensitivity.rb +53 -0
  11. data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +83 -0
  12. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +186 -0
  13. data/lib/authlogic/acts_as_authentic/single_access_token.rb +83 -0
  14. data/lib/authlogic/config.rb +43 -0
  15. data/lib/authlogic/controller_adapters/abstract_adapter.rb +119 -0
  16. data/lib/authlogic/controller_adapters/rack_adapter.rb +72 -0
  17. data/lib/authlogic/controller_adapters/rails_adapter.rb +47 -0
  18. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +67 -0
  19. data/lib/authlogic/cookie_credentials.rb +63 -0
  20. data/lib/authlogic/crypto_providers/bcrypt.rb +113 -0
  21. data/lib/authlogic/crypto_providers/md5/v2.rb +35 -0
  22. data/lib/authlogic/crypto_providers/md5.rb +36 -0
  23. data/lib/authlogic/crypto_providers/scrypt.rb +92 -0
  24. data/lib/authlogic/crypto_providers/sha1/v2.rb +41 -0
  25. data/lib/authlogic/crypto_providers/sha1.rb +42 -0
  26. data/lib/authlogic/crypto_providers/sha256/v2.rb +58 -0
  27. data/lib/authlogic/crypto_providers/sha256.rb +59 -0
  28. data/lib/authlogic/crypto_providers/sha512/v2.rb +39 -0
  29. data/lib/authlogic/crypto_providers/sha512.rb +38 -0
  30. data/lib/authlogic/crypto_providers.rb +87 -0
  31. data/lib/authlogic/errors.rb +50 -0
  32. data/lib/authlogic/i18n/translator.rb +18 -0
  33. data/lib/authlogic/i18n.rb +100 -0
  34. data/lib/authlogic/random.rb +18 -0
  35. data/lib/authlogic/session/base.rb +2207 -0
  36. data/lib/authlogic/session/magic_column/assigns_last_request_at.rb +46 -0
  37. data/lib/authlogic/test_case/mock_api_controller.rb +52 -0
  38. data/lib/authlogic/test_case/mock_controller.rb +58 -0
  39. data/lib/authlogic/test_case/mock_cookie_jar.rb +109 -0
  40. data/lib/authlogic/test_case/mock_logger.rb +12 -0
  41. data/lib/authlogic/test_case/mock_request.rb +35 -0
  42. data/lib/authlogic/test_case/rails_request_adapter.rb +39 -0
  43. data/lib/authlogic/test_case.rb +215 -0
  44. data/lib/authlogic/version.rb +22 -0
  45. data/lib/authlogic.rb +44 -0
  46. metadata +382 -0
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ module ActsAsAuthentic
5
+ # This is one of my favorite features that I think is pretty cool. It's
6
+ # things like this that make a library great and let you know you are on the
7
+ # right track.
8
+ #
9
+ # Just to clear up any confusion, Authlogic stores both the record id and
10
+ # the persistence token in the session. Why? So stale sessions can not be
11
+ # persisted. It stores the id so it can quickly find the record, and the
12
+ # persistence token to ensure no sessions are stale. So if the persistence
13
+ # token changes, the user must log back in.
14
+ #
15
+ # Well, the persistence token changes with the password. What happens if the
16
+ # user changes his own password? He shouldn't have to log back in, he's the
17
+ # one that made the change.
18
+ #
19
+ # That being said, wouldn't it be nice if their session and cookie
20
+ # information was automatically updated? Instead of cluttering up your
21
+ # controller with redundant session code. The same thing goes for new
22
+ # registrations.
23
+ #
24
+ # That's what this module is all about. This will automatically maintain the
25
+ # cookie and session values as records are saved.
26
+ module SessionMaintenance
27
+ def self.included(klass)
28
+ klass.class_eval do
29
+ extend Config
30
+ add_acts_as_authentic_module(Methods)
31
+ end
32
+ end
33
+
34
+ # Configuration for the session maintenance aspect of acts_as_authentic.
35
+ # These methods become class methods of ::ActiveRecord::Base.
36
+ module Config
37
+ # In order to turn off automatic maintenance of sessions
38
+ # after create, just set this to false.
39
+ #
40
+ # * <tt>Default:</tt> true
41
+ # * <tt>Accepts:</tt> Boolean
42
+ def log_in_after_create(value = nil)
43
+ rw_config(:log_in_after_create, value, true)
44
+ end
45
+ alias log_in_after_create= log_in_after_create
46
+
47
+ # In order to turn off automatic maintenance of sessions when updating
48
+ # the password, just set this to false.
49
+ #
50
+ # * <tt>Default:</tt> true
51
+ # * <tt>Accepts:</tt> Boolean
52
+ def log_in_after_password_change(value = nil)
53
+ rw_config(:log_in_after_password_change, value, true)
54
+ end
55
+ alias log_in_after_password_change= log_in_after_password_change
56
+
57
+ # As you may know, authlogic sessions can be separate by id (See
58
+ # Authlogic::Session::Base#id). You can specify here what session ids
59
+ # you want auto maintained. By default it is the main session, which has
60
+ # an id of nil.
61
+ #
62
+ # * <tt>Default:</tt> [nil]
63
+ # * <tt>Accepts:</tt> Array
64
+ def session_ids(value = nil)
65
+ rw_config(:session_ids, value, [nil])
66
+ end
67
+ alias session_ids= session_ids
68
+
69
+ # The name of the associated session class. This is inferred by the name
70
+ # of the model.
71
+ #
72
+ # * <tt>Default:</tt> "#{klass.name}Session".constantize
73
+ # * <tt>Accepts:</tt> Class
74
+ def session_class(value = nil)
75
+ const = begin
76
+ "#{base_class.name}Session".constantize
77
+ rescue NameError
78
+ nil
79
+ end
80
+ rw_config(:session_class, value, const)
81
+ end
82
+ alias session_class= session_class
83
+ end
84
+
85
+ # This module, as one of the `acts_as_authentic_modules`, is only included
86
+ # into an ActiveRecord model if that model calls `acts_as_authentic`.
87
+ module Methods
88
+ def self.included(klass)
89
+ klass.class_eval do
90
+ before_save :get_session_information, if: :update_sessions?
91
+ before_save :maintain_sessions, if: :update_sessions?
92
+ end
93
+ end
94
+
95
+ # Save the record and skip session maintenance all together.
96
+ def save_without_session_maintenance(**options)
97
+ self.skip_session_maintenance = true
98
+ result = save(**options)
99
+ self.skip_session_maintenance = false
100
+ result
101
+ end
102
+
103
+ private
104
+
105
+ def skip_session_maintenance=(value)
106
+ @skip_session_maintenance = value
107
+ end
108
+
109
+ def skip_session_maintenance
110
+ @skip_session_maintenance ||= false
111
+ end
112
+
113
+ def update_sessions?
114
+ !skip_session_maintenance &&
115
+ session_class &&
116
+ session_class.activated? &&
117
+ maintain_session? &&
118
+ !session_ids.blank? &&
119
+ will_save_change_to_persistence_token?
120
+ end
121
+
122
+ def maintain_session?
123
+ log_in_after_create? || log_in_after_password_change?
124
+ end
125
+
126
+ def get_session_information
127
+ # Need to determine if we are completely logged out, or logged in as
128
+ # another user.
129
+ @_sessions = []
130
+
131
+ session_ids.each do |session_id|
132
+ session = session_class.find(session_id, self)
133
+ @_sessions << session if session&.record
134
+ end
135
+ end
136
+
137
+ def maintain_sessions
138
+ if @_sessions.empty?
139
+ create_session
140
+ else
141
+ update_sessions
142
+ end
143
+ end
144
+
145
+ def create_session
146
+ # We only want to automatically login into the first session, since
147
+ # this is the main session. The other sessions are sessions that
148
+ # need to be created after logging into the main session.
149
+ session_id = session_ids.first
150
+ session_class.create(*[self, self, session_id].compact)
151
+
152
+ true
153
+ end
154
+
155
+ def update_sessions
156
+ # We found sessions above, let's update them with the new info
157
+ @_sessions.each do |stale_session|
158
+ next if stale_session.record != self
159
+ stale_session.unauthorized_record = self
160
+ stale_session.save
161
+ end
162
+
163
+ true
164
+ end
165
+
166
+ def session_ids
167
+ self.class.session_ids
168
+ end
169
+
170
+ def session_class
171
+ self.class.session_class
172
+ end
173
+
174
+ def log_in_after_create?
175
+ new_record? && self.class.log_in_after_create
176
+ end
177
+
178
+ def log_in_after_password_change?
179
+ persisted? &&
180
+ will_save_change_to_persistence_token? &&
181
+ self.class.log_in_after_password_change
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ module ActsAsAuthentic
5
+ # This module is responsible for maintaining the single_access token. For
6
+ # more information the single access token and how to use it, see "Params"
7
+ # in `Session::Base`.
8
+ module SingleAccessToken
9
+ def self.included(klass)
10
+ klass.class_eval do
11
+ extend Config
12
+ add_acts_as_authentic_module(Methods)
13
+ end
14
+ end
15
+
16
+ # All configuration for the single_access token aspect of acts_as_authentic.
17
+ #
18
+ # These methods become class methods of ::ActiveRecord::Base.
19
+ module Config
20
+ # The single access token is used for authentication via URLs, such as a private
21
+ # feed. That being said, if the user changes their password, that token probably
22
+ # shouldn't change. If it did, the user would have to update all of their URLs. So
23
+ # be default this is option is disabled, if you need it, feel free to turn it on.
24
+ #
25
+ # * <tt>Default:</tt> false
26
+ # * <tt>Accepts:</tt> Boolean
27
+ def change_single_access_token_with_password(value = nil)
28
+ rw_config(:change_single_access_token_with_password, value, false)
29
+ end
30
+ alias change_single_access_token_with_password= change_single_access_token_with_password
31
+ end
32
+
33
+ # All method, for the single_access token aspect of acts_as_authentic.
34
+ #
35
+ # This module, as one of the `acts_as_authentic_modules`, is only included
36
+ # into an ActiveRecord model if that model calls `acts_as_authentic`.
37
+ module Methods
38
+ def self.included(klass)
39
+ return unless klass.column_names.include?("single_access_token")
40
+
41
+ klass.class_eval do
42
+ include InstanceMethods
43
+ validates_uniqueness_of :single_access_token,
44
+ case_sensitive: true,
45
+ if: :will_save_change_to_single_access_token?
46
+
47
+ before_validation :reset_single_access_token, if: :reset_single_access_token?
48
+ if respond_to?(:after_password_set)
49
+ after_password_set(
50
+ :reset_single_access_token,
51
+ if: :change_single_access_token_with_password?
52
+ )
53
+ end
54
+ end
55
+ end
56
+
57
+ # :nodoc:
58
+ module InstanceMethods
59
+ # Resets the single_access_token to a random friendly token.
60
+ def reset_single_access_token
61
+ self.single_access_token = Authlogic::Random.friendly_token
62
+ end
63
+
64
+ # same as reset_single_access_token, but then saves the record.
65
+ def reset_single_access_token!
66
+ reset_single_access_token
67
+ save_without_session_maintenance
68
+ end
69
+
70
+ protected
71
+
72
+ def reset_single_access_token?
73
+ single_access_token.blank?
74
+ end
75
+
76
+ def change_single_access_token_with_password?
77
+ self.class.change_single_access_token_with_password == true
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ # Mixed into `Authlogic::ActsAsAuthentic::Base` and
5
+ # `Authlogic::Session::Base`.
6
+ module Config
7
+ E_USE_NORMAL_RAILS_VALIDATION = <<~EOS
8
+ This Authlogic configuration option (%s) is deprecated. Use normal
9
+ ActiveRecord validation instead. Detailed instructions:
10
+ https://github.com/binarylogic/authlogic/blob/master/doc/use_normal_rails_validation.md
11
+ EOS
12
+
13
+ def self.extended(klass)
14
+ klass.class_eval do
15
+ # TODO: Is this a confusing name, given this module is mixed into
16
+ # both `Authlogic::ActsAsAuthentic::Base` and
17
+ # `Authlogic::Session::Base`? Perhaps a more generic name, like
18
+ # `authlogic_config` would be better?
19
+ class_attribute :acts_as_authentic_config
20
+ self.acts_as_authentic_config ||= {}
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def deprecate_authlogic_config(method_name)
27
+ ::ActiveSupport::Deprecation.warn(
28
+ format(E_USE_NORMAL_RAILS_VALIDATION, method_name)
29
+ )
30
+ end
31
+
32
+ # This is a one-liner method to write a config setting, read the config
33
+ # setting, and also set a default value for the setting.
34
+ def rw_config(key, value, default_value = nil)
35
+ if value.nil?
36
+ acts_as_authentic_config.include?(key) ? acts_as_authentic_config[key] : default_value
37
+ else
38
+ self.acts_as_authentic_config = acts_as_authentic_config.merge(key => value)
39
+ value
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ module ControllerAdapters # :nodoc:
5
+ # Allows you to use Authlogic in any framework you want, not just rails. See
6
+ # the RailsAdapter for an example of how to adapt Authlogic to work with
7
+ # your framework.
8
+ class AbstractAdapter
9
+ E_COOKIE_DOMAIN_ADAPTER = "The cookie_domain method has not been " \
10
+ "implemented by the controller adapter"
11
+ ENV_SESSION_OPTIONS = "rack.session.options"
12
+
13
+ attr_accessor :controller
14
+
15
+ def initialize(controller)
16
+ self.controller = controller
17
+ end
18
+
19
+ def authenticate_with_http_basic
20
+ @auth = Rack::Auth::Basic::Request.new(controller.request.env)
21
+ if @auth.provided? && @auth.basic?
22
+ yield(*@auth.credentials)
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ def cookies
29
+ controller.cookies
30
+ end
31
+
32
+ def cookie_domain
33
+ raise NotImplementedError, E_COOKIE_DOMAIN_ADAPTER
34
+ end
35
+
36
+ def params
37
+ controller.params
38
+ end
39
+
40
+ def request
41
+ controller.request
42
+ end
43
+
44
+ def request_content_type
45
+ request.content_type
46
+ end
47
+
48
+ # Inform Rack that we would like a new session ID to be assigned. Changes
49
+ # the ID, but not the contents of the session.
50
+ #
51
+ # The `:renew` option is read by `rack/session/abstract/id.rb`.
52
+ #
53
+ # This is how Devise (via warden) implements defense against Session
54
+ # Fixation. Our implementation is copied directly from the warden gem
55
+ # (set_user in warden/proxy.rb)
56
+ def renew_session_id
57
+ env = request.env
58
+ options = env[ENV_SESSION_OPTIONS]
59
+ if options
60
+ if options.frozen?
61
+ env[ENV_SESSION_OPTIONS] = options.merge(renew: true).freeze
62
+ else
63
+ options[:renew] = true
64
+ end
65
+ end
66
+ end
67
+
68
+ def session
69
+ controller.session
70
+ end
71
+
72
+ def responds_to_single_access_allowed?
73
+ controller.respond_to?(:single_access_allowed?, true)
74
+ end
75
+
76
+ def single_access_allowed?
77
+ controller.send(:single_access_allowed?)
78
+ end
79
+
80
+ # You can disable the updating of `last_request_at`
81
+ # on a per-controller basis.
82
+ #
83
+ # # in your controller
84
+ # def last_request_update_allowed?
85
+ # false
86
+ # end
87
+ #
88
+ # For example, what if you had a javascript function that polled the
89
+ # server updating how much time is left in their session before it
90
+ # times out. Obviously you would want to ignore this request, because
91
+ # then the user would never time out. So you can do something like
92
+ # this in your controller:
93
+ #
94
+ # def last_request_update_allowed?
95
+ # action_name != "update_session_time_left"
96
+ # end
97
+ #
98
+ # See `authlogic/session/magic_columns.rb` to learn more about the
99
+ # `last_request_at` column itself.
100
+ def last_request_update_allowed?
101
+ if controller.respond_to?(:last_request_update_allowed?, true)
102
+ controller.send(:last_request_update_allowed?)
103
+ else
104
+ true
105
+ end
106
+ end
107
+
108
+ def respond_to_missing?(*args)
109
+ super(*args) || controller.respond_to?(*args)
110
+ end
111
+
112
+ private
113
+
114
+ def method_missing(id, *args, &block)
115
+ controller.send(id, *args, &block)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ module ControllerAdapters
5
+ # Adapter for authlogic to make it function as a Rack middleware.
6
+ # First you'll have write your own Rack adapter where you have to set your cookie domain.
7
+ #
8
+ # class YourRackAdapter < Authlogic::ControllerAdapters::RackAdapter
9
+ # def cookie_domain
10
+ # 'your_cookie_domain_here.com'
11
+ # end
12
+ # end
13
+ #
14
+ # Next you need to set up a rack middleware like this:
15
+ #
16
+ # class AuthlogicMiddleware
17
+ # def initialize(app)
18
+ # @app = app
19
+ # end
20
+ #
21
+ # def call(env)
22
+ # YourRackAdapter.new(env)
23
+ # @app.call(env)
24
+ # end
25
+ # end
26
+ #
27
+ # And that is all! Now just load this middleware into rack:
28
+ #
29
+ # use AuthlogicMiddleware
30
+ #
31
+ # Authlogic will expect a User and a UserSession object to be present:
32
+ #
33
+ # class UserSession < Authlogic::Session::Base
34
+ # # Authlogic options go here
35
+ # end
36
+ #
37
+ # class User < ApplicationRecord
38
+ # acts_as_authentic
39
+ # end
40
+ #
41
+ class RackAdapter < AbstractAdapter
42
+ def initialize(env)
43
+ # We use the Rack::Request object as the controller object.
44
+ # For this to work, we have to add some glue.
45
+ request = Rack::Request.new(env)
46
+
47
+ request.instance_eval do
48
+ def request
49
+ self
50
+ end
51
+
52
+ def remote_ip
53
+ ip
54
+ end
55
+ end
56
+
57
+ super(request)
58
+ Authlogic::Session::Base.controller = self
59
+ end
60
+
61
+ # Rack Requests stores cookies with not just the value, but also with
62
+ # flags and expire information in the hash. Authlogic does not like this,
63
+ # so we drop everything except the cookie value.
64
+ def cookies
65
+ controller
66
+ .cookies
67
+ .map { |key, value_hash| { key => value_hash[:value] } }
68
+ .inject(:merge) || {}
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ module ControllerAdapters
5
+ # Adapts authlogic to work with rails. The point is to close the gap between
6
+ # what authlogic expects and what the rails controller object provides.
7
+ # Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite,
8
+ # etc.
9
+ class RailsAdapter < AbstractAdapter
10
+ def authenticate_with_http_basic(&block)
11
+ controller.authenticate_with_http_basic(&block)
12
+ end
13
+
14
+ # Returns a `ActionDispatch::Cookies::CookieJar`. See the AC guide
15
+ # http://guides.rubyonrails.org/action_controller_overview.html#cookies
16
+ def cookies
17
+ controller.respond_to?(:cookies, true) ? controller.send(:cookies) : nil
18
+ end
19
+
20
+ def cookie_domain
21
+ controller.request.session_options[:domain]
22
+ end
23
+
24
+ def request_content_type
25
+ request.format.to_s
26
+ end
27
+
28
+ # Lets Authlogic know about the controller object via a before filter, AKA
29
+ # "activates" authlogic.
30
+ module RailsImplementation
31
+ def self.included(klass) # :nodoc:
32
+ klass.prepend_before_action :activate_authlogic
33
+ end
34
+
35
+ private
36
+
37
+ def activate_authlogic
38
+ Authlogic::Session::Base.controller = RailsAdapter.new(self)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ ActiveSupport.on_load(:action_controller) do
46
+ include Authlogic::ControllerAdapters::RailsAdapter::RailsImplementation
47
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Authlogic bridge for Sinatra
4
+ module Authlogic
5
+ module ControllerAdapters
6
+ module SinatraAdapter
7
+ # Cookie management functions
8
+ class Cookies
9
+ attr_reader :request, :response
10
+
11
+ def initialize(request, response)
12
+ @request = request
13
+ @response = response
14
+ end
15
+
16
+ def delete(key, options = {})
17
+ @response.delete_cookie(key, options)
18
+ end
19
+
20
+ def []=(key, options)
21
+ @response.set_cookie(key, options)
22
+ end
23
+
24
+ def method_missing(meth, *args, &block)
25
+ @request.cookies.send(meth, *args, &block)
26
+ end
27
+ end
28
+
29
+ # Thin wrapper around request and response.
30
+ class Controller
31
+ attr_reader :request, :response, :cookies
32
+
33
+ def initialize(request, response)
34
+ @request = request
35
+ @cookies = Cookies.new(request, response)
36
+ end
37
+
38
+ def session
39
+ env["rack.session"]
40
+ end
41
+
42
+ def method_missing(meth, *args, &block)
43
+ @request.send meth, *args, &block
44
+ end
45
+ end
46
+
47
+ # Sinatra controller adapter
48
+ class Adapter < AbstractAdapter
49
+ def cookie_domain
50
+ env["SERVER_NAME"]
51
+ end
52
+
53
+ # Mixed into `Sinatra::Base`
54
+ module Implementation
55
+ def self.included(klass)
56
+ klass.send :before do
57
+ controller = Controller.new(request, response)
58
+ Authlogic::Session::Base.controller = Adapter.new(controller)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ Sinatra::Base.send(:include, Authlogic::ControllerAdapters::SinatraAdapter::Adapter::Implementation)
@@ -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