authlogic-nicho 6.5

Sign up to get free protection for your applications and to get access to all the features.
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