diff --git a/lib/devise/models/rememberable.rb b/lib/devise/models/rememberable.rb index a66979ad59..e20d8dd03e 100644 --- a/lib/devise/models/rememberable.rb +++ b/lib/devise/models/rememberable.rb @@ -101,9 +101,13 @@ def after_remembered end def remember_me?(token, generated_at) - # TODO: Normalize the JSON type coercion along with the Timeoutable hook - # in a single place https://github.com/heartcombo/devise/blob/ffe9d6d406e79108cf32a2c6a1d0b3828849c40b/lib/devise/hooks/timeoutable.rb#L14-L18 - if generated_at.is_a?(String) + # Normalize cookie/session JSON type coercion the same way Timeoutable + # does for last_request_at (Integer from to_i, Float/numeric strings + # from to_f / to_json). + case generated_at + when Integer, Float + generated_at = Time.at(generated_at).utc + when String generated_at = time_from_json(generated_at) end @@ -122,7 +126,7 @@ def remember_me?(token, generated_at) private def time_from_json(value) - if value =~ /\A\d+\.\d+\Z/ + if value =~ /\A\d+(?:\.\d+)?\Z/ Time.at(value.to_f) else Time.parse(value) rescue nil diff --git a/test/models/rememberable_test.rb b/test/models/rememberable_test.rb index 8b83172120..a1cc9fd45a 100644 --- a/test/models/rememberable_test.rb +++ b/test/models/rememberable_test.rb @@ -66,6 +66,23 @@ def create_resource assert_equal user, User.serialize_from_cookie(user.to_key, user.authenticatable_salt, Time.now.utc.to_f.to_json) end + test 'serialize from cookie should accept Integer and Float timestamps from JSON coercion' do + user = create_user + user.remember_me! + assert_equal user, User.serialize_from_cookie(user.to_key, user.authenticatable_salt, Time.now.utc.to_f) + + # Integer seconds truncate subseconds, so keep remember_created_at clearly earlier. + user.update_attribute(:remember_created_at, 1.minute.ago) + assert_equal user, User.serialize_from_cookie(user.to_key, user.authenticatable_salt, Time.now.utc.to_i) + end + + test 'serialize from cookie should accept an Integer-only numeric String timestamp' do + user = create_user + user.remember_me! + user.update_attribute(:remember_created_at, 1.minute.ago) + assert_equal user, User.serialize_from_cookie(user.to_key, user.authenticatable_salt, Time.now.utc.to_i.to_s) + end + test 'serialize from cookie should return nil with invalid datetime' do user = create_user user.remember_me!