Skip to content

Commit c929966

Browse files
author
José Valim
committed
Store creation timestamp on remember cookies
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
1 parent ba5dd0a commit c929966

7 files changed

Lines changed: 66 additions & 142 deletions

File tree

lib/devise.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ module Strategies
116116
mattr_accessor :remember_for
117117
@@remember_for = 2.weeks
118118

119+
# TODO: extend_remember_period is no longer used
119120
# If true, extends the user's remember period when remembered via cookie.
120121
mattr_accessor :extend_remember_period
121122
@@extend_remember_period = false

lib/devise/controllers/rememberable.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def self.cookie_values
1313
def remember_me(resource)
1414
return if env["devise.skip_storage"]
1515
scope = Devise::Mapping.find_scope!(resource)
16-
resource.remember_me!(resource.extend_remember_period)
16+
resource.remember_me!
1717
cookies.signed[remember_key(resource, scope)] = remember_cookie_values(resource)
1818
end
1919

lib/devise/models/rememberable.rb

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,25 @@ def self.required_fields(klass)
4545
[:remember_created_at]
4646
end
4747

48-
# Generate a new remember token and save the record without validations
49-
# if remember expired (token is no longer valid) or extend_remember_period is true
50-
def remember_me!(extend_period=false)
51-
self.remember_token = self.class.remember_token if generate_remember_token?
52-
self.remember_created_at = Time.now.utc if generate_remember_timestamp?(extend_period)
48+
# TODO: We were used to receive a extend period argument but we no longer do.
49+
# Remove this for Devise 4.0.
50+
def remember_me!(*)
51+
self.remember_token = self.class.remember_token if respond_to?(:remember_token)
52+
self.remember_created_at ||= Time.now.utc
5353
save(validate: false) if self.changed?
5454
end
5555

5656
# If the record is persisted, remove the remember token (but only if
5757
# it exists), and save the record without validations.
5858
def forget_me!
5959
return unless persisted?
60-
self.remember_token = nil if respond_to?(:remember_token=)
60+
self.remember_token = nil if respond_to?(:remember_token)
6161
self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out
6262
save(validate: false)
6363
end
6464

65-
# Remember token should be expired if expiration time not overpass now.
66-
def remember_expired?
67-
remember_created_at.nil? || (remember_expires_at <= Time.now.utc)
68-
end
69-
70-
# Remember token expires at created time + remember_for configuration
7165
def remember_expires_at
72-
remember_created_at + self.class.remember_for
66+
self.class.remember_for.from_now
7367
end
7468

7569
def rememberable_value
@@ -104,27 +98,30 @@ def after_remembered
10498

10599
protected
106100

107-
def generate_remember_token? #:nodoc:
108-
respond_to?(:remember_token) && remember_expired?
109-
end
110-
111-
# Generate a timestamp if extend_remember_period is true, if no remember_token
112-
# exists, or if an existing remember token has expired.
113-
def generate_remember_timestamp?(extend_period) #:nodoc:
114-
extend_period || remember_expired?
115-
end
116-
117101
module ClassMethods
118102
# Create the cookie key using the record id and remember_token
119103
def serialize_into_cookie(record)
120-
[record.to_key, record.rememberable_value]
104+
[record.to_key, record.rememberable_value, Time.now.utc]
121105
end
122106

123107
# Recreate the user based on the stored cookie
124-
def serialize_from_cookie(id, remember_token)
125-
record = to_adapter.get(id)
126-
record if record && !record.remember_expired? &&
127-
Devise.secure_compare(record.rememberable_value, remember_token)
108+
def serialize_from_cookie(*args)
109+
id, token, generated_at = args
110+
111+
# The token is only valid if:
112+
# 1. we have a date
113+
# 2. the current time does not pass the expiry period
114+
# 3. there is a record with the given id
115+
# 4. the record has a remember_created_at date
116+
# 5. the token date is bigger than the remember_created_at
117+
# 6. the token matches
118+
if generated_at &&
119+
(self.remember_for.ago < generated_at) &&
120+
(record = to_adapter.get(id)) &&
121+
(generated_at > (record.remember_created_at || Time.now).utc) &&
122+
Devise.secure_compare(record.rememberable_value, token)
123+
record
124+
end
128125
end
129126

130127
# Generate a token checking if one does not already exist in the database.
@@ -135,6 +132,7 @@ def remember_token #:nodoc:
135132
end
136133
end
137134

135+
# TODO: extend_remember_period is no longer used
138136
Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options, :expire_all_remember_me_on_sign_out)
139137
end
140138
end

lib/devise/models/timeoutable.rb

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def self.required_fields(klass)
2626

2727
# Checks whether the user session has expired based on configured time.
2828
def timedout?(last_access)
29-
return false if remember_exists_and_not_expired?
3029
!timeout_in.nil? && last_access && last_access <= timeout_in.ago
3130
end
3231

@@ -36,11 +35,6 @@ def timeout_in
3635

3736
private
3837

39-
def remember_exists_and_not_expired?
40-
return false unless respond_to?(:remember_created_at) && respond_to?(:remember_expired?)
41-
remember_created_at && !remember_expired?
42-
end
43-
4438
module ClassMethods
4539
Devise::Models.config(self, :timeout_in)
4640
end

test/integration/rememberable_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class RememberMeTest < ActionDispatch::IntegrationTest
44
def create_user_and_remember(add_to_token='')
55
user = create_user
66
user.remember_me!
7-
raw_cookie = User.serialize_into_cookie(user).tap { |a| a.last << add_to_token }
7+
raw_cookie = User.serialize_into_cookie(user).tap { |a| a[1] << add_to_token }
88
cookies['remember_user_token'] = generate_signed_cookie(raw_cookie)
99
user
1010
end
@@ -135,7 +135,7 @@ def cookie_expires(key)
135135

136136
test 'do not remember with expired token' do
137137
create_user_and_remember
138-
swap Devise, remember_for: 0 do
138+
swap Devise, remember_for: 0.days do
139139
get users_path
140140
assert_not warden.authenticated?(:user)
141141
assert_redirected_to new_user_session_path

test/integration/timeoutable_test.rb

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,6 @@ def last_request_at
165165
end
166166
end
167167

168-
test 'time out not triggered if remembered' do
169-
user = sign_in_as_user remember_me: true
170-
get expire_user_path(user)
171-
assert_not_nil last_request_at
172-
173-
get users_path
174-
assert_response :success
175-
assert warden.authenticated?(:user)
176-
end
177-
178168
test 'does not crashes when the last_request_at is a String' do
179169
user = sign_in_as_user
180170

test/models/rememberable_test.rb

Lines changed: 36 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def create_resource
1313
user = create_user
1414
user.expects(:valid?).never
1515
user.remember_me!
16+
assert user.remember_created_at
1617
end
1718

1819
test 'forget_me should not clear remember token if using salt' do
@@ -33,13 +34,45 @@ def create_resource
3334
test 'serialize into cookie' do
3435
user = create_user
3536
user.remember_me!
36-
assert_equal [user.to_key, user.authenticatable_salt], User.serialize_into_cookie(user)
37+
id, token, date = User.serialize_into_cookie(user)
38+
assert_equal id, user.to_key
39+
assert_equal token, user.authenticatable_salt
40+
assert date.is_a?(Time)
3741
end
3842

3943
test 'serialize from cookie' do
4044
user = create_user
4145
user.remember_me!
42-
assert_equal user, User.serialize_from_cookie(user.to_key, user.authenticatable_salt)
46+
assert_equal user, User.serialize_from_cookie(user.to_key, user.authenticatable_salt, Time.now.utc)
47+
end
48+
49+
test 'serialize from cookie should return nil if no resource is found' do
50+
assert_nil resource_class.serialize_from_cookie([0], "123", Time.now.utc)
51+
end
52+
53+
test 'serialize from cookie should return nil if no timestamp' do
54+
user = create_user
55+
user.remember_me!
56+
assert_nil User.serialize_from_cookie(user.to_key, user.authenticatable_salt)
57+
end
58+
59+
test 'serialize from cookie should return nil if timestamp is earlier than token creation' do
60+
user = create_user
61+
user.remember_me!
62+
assert_nil User.serialize_from_cookie(user.to_key, user.authenticatable_salt, 1.day.ago)
63+
end
64+
65+
test 'serialize from cookie should return nil if timestamp is older than remember_for' do
66+
user = create_user
67+
user.remember_created_at = 1.month.ago
68+
user.remember_me!
69+
assert_nil User.serialize_from_cookie(user.to_key, user.authenticatable_salt, 3.weeks.ago)
70+
end
71+
72+
test 'serialize from cookie me return nil if is a valid resource with invalid token' do
73+
user = create_user
74+
user.remember_me!
75+
assert_nil User.serialize_from_cookie(user.to_key, "123", Time.now.utc)
4376
end
4477

4578
test 'raises a RuntimeError if authenticatable_salt is nil or empty' do
@@ -93,28 +126,7 @@ def user.authenticable_salt; ""; end
93126
resource.forget_me!
94127
end
95128

96-
test 'remember is expired if not created at timestamp is set' do
97-
assert create_resource.remember_expired?
98-
end
99-
100-
test 'serialize should return nil if no resource is found' do
101-
assert_nil resource_class.serialize_from_cookie([0], "123")
102-
end
103-
104-
test 'remember me return nil if is a valid resource with invalid token' do
105-
resource = create_resource
106-
assert_nil resource_class.serialize_from_cookie([resource.id], "123")
107-
end
108-
109-
test 'remember for should fallback to devise remember for default configuration' do
110-
swap Devise, remember_for: 1.day do
111-
resource = create_resource
112-
resource.remember_me!
113-
assert_not resource.remember_expired?
114-
end
115-
end
116-
117-
test 'remember expires at should sum date of creation with remember for configuration' do
129+
test 'remember expires at uses remember for configuration' do
118130
swap Devise, remember_for: 3.days do
119131
resource = create_resource
120132
resource.remember_me!
@@ -125,77 +137,6 @@ def user.authenticable_salt; ""; end
125137
end
126138
end
127139

128-
test 'remember should be expired if remember_for is zero' do
129-
swap Devise, remember_for: 0.days do
130-
Devise.remember_for = 0.days
131-
resource = create_resource
132-
resource.remember_me!
133-
assert resource.remember_expired?
134-
end
135-
end
136-
137-
test 'remember should be expired if it was created before limit time' do
138-
swap Devise, remember_for: 1.day do
139-
resource = create_resource
140-
resource.remember_me!
141-
resource.remember_created_at = 2.days.ago
142-
resource.save
143-
assert resource.remember_expired?
144-
end
145-
end
146-
147-
test 'remember should not be expired if it was created within the limit time' do
148-
swap Devise, remember_for: 30.days do
149-
resource = create_resource
150-
resource.remember_me!
151-
resource.remember_created_at = (30.days.ago + 2.minutes)
152-
resource.save
153-
assert_not resource.remember_expired?
154-
end
155-
end
156-
157-
test 'if extend_remember_period is false, remember_me! should generate a new timestamp if expired' do
158-
swap Devise, remember_for: 5.minutes do
159-
resource = create_resource
160-
resource.remember_me!(false)
161-
assert resource.remember_created_at
162-
163-
resource.remember_created_at = old = 10.minutes.ago
164-
resource.save
165-
166-
resource.remember_me!(false)
167-
assert_not_equal old.to_i, resource.remember_created_at.to_i
168-
end
169-
end
170-
171-
test 'if extend_remember_period is false, remember_me! should not generate a new timestamp' do
172-
swap Devise, remember_for: 1.year do
173-
resource = create_resource
174-
resource.remember_me!(false)
175-
assert resource.remember_created_at
176-
177-
resource.remember_created_at = old = 10.minutes.ago.utc
178-
resource.save
179-
180-
resource.remember_me!(false)
181-
assert_equal old.to_i, resource.remember_created_at.to_i
182-
end
183-
end
184-
185-
test 'if extend_remember_period is true, remember_me! should always generate a new timestamp' do
186-
swap Devise, remember_for: 1.year do
187-
resource = create_resource
188-
resource.remember_me!(true)
189-
assert resource.remember_created_at
190-
191-
resource.remember_created_at = old = 10.minutes.ago
192-
resource.save
193-
194-
resource.remember_me!(true)
195-
assert_not_equal old, resource.remember_created_at
196-
end
197-
end
198-
199140
test 'should have the required_fields array' do
200141
assert_same_content Devise::Models::Rememberable.required_fields(User), [
201142
:remember_created_at

0 commit comments

Comments
 (0)