Skip to content

Commit

Permalink
Migrate to a signed, non-http-only "user_token-v2" cookie
Browse files Browse the repository at this point in the history
We need access to the user_token from javascript as well... for upcoming
work.

The cleanest way to get there is to introduce an updated version of the
"user_token" cookie: "user_token-v2" since:
- there's no way to detect if a cookie is http-only (ugh).
- we've already previously updated from unsigned to signed version of
  "user_token" cookie--for *some* users, but probably not for most.
- the "user_token" cookie is permanent for this site...

Once updated to "user_token-v2", the old "user_token" cookie is deleted.

Note: Since the existing "user_token" cookie is permanent, we have to
keep this script around "indefinitely".
  • Loading branch information
pdobb committed Jan 28, 2025
1 parent 56e40b3 commit 4d885bf
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 29 deletions.
45 changes: 22 additions & 23 deletions app/models/current_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# just the GUID generated by the DB ({User#id}). Which is then "permanently"
# stored into `cookies.signed[CurrentUser::COOKIE]` for future lookup.
class CurrentUser
COOKIE = :user_token
COOKIE = "user_token-v2"

include CallMethodBehaviors

Expand All @@ -17,7 +17,7 @@ def initialize(context:)
end

def call
MigrateToSignedUserTokenCookie.(context:) # TODO: Remove when site != beta.
MigrateFromOldUserTokenCookie.(context:)

if stored_user_token?
find || create
Expand All @@ -40,10 +40,7 @@ def find
end

def stored_user_token? = stored_user_token.present?

def stored_user_token
@stored_user_token ||= cookies.signed[COOKIE]
end
def stored_user_token = @stored_user_token ||= cookies.signed[COOKIE]

def create
User.create(user_agent:).tap { |new_user|
Expand All @@ -54,15 +51,15 @@ def create
def user_agent = context.user_agent

def store_user_token(value:)
context.store_signed_http_cookie(COOKIE, value:)
context.store_signed_cookie(COOKIE, value:)
end

# CurrentUser::MigrateToSignedUserTokenCookie is a temporary service object
# for migrating existing users over from unsigned to signed "user_token"
# cookie storage.
#
# TODO: Remove when site != beta.
class MigrateToSignedUserTokenCookie
# CurrentUser::MigrateFromOldUserTokenCookie migrates old users over from the
# "user_token" cookie to the new "user_token-v2" cookie for identifying the
# current {User}. Really old users will have an unsigned cookie.
class MigrateFromOldUserTokenCookie
OLD_COOKIE = "user_token"

include CallMethodBehaviors

def initialize(context:)
Expand All @@ -71,27 +68,29 @@ def initialize(context:)

# :reek:TooManyStatements
def call
return if new_signed_user_token?
return unless old_unsigned_user_token?
return if new_user_token?
return unless old_user_token?

user_token = old_user_token
delete_old_cookie
store_new_signed_cookie(value: user_token)
delete_old_cookie
end

private

def new_signed_user_token? = cookies.signed[COOKIE].present?
def old_unsigned_user_token? = old_user_token.present?

def old_user_token = @old_user_token ||= cookies[COOKIE]
def new_user_token? = cookies.signed[COOKIE].present?
def old_user_token? = old_user_token.present?

def delete_old_cookie
cookies.delete(COOKIE)
def old_user_token
@old_user_token ||= cookies.signed[OLD_COOKIE] || cookies[OLD_COOKIE]
end

def store_new_signed_cookie(value:)
context.store_signed_http_cookie(COOKIE, value:)
context.store_signed_cookie(COOKIE, value:)
end

def delete_old_cookie
cookies.delete(OLD_COOKIE)
end

attr_reader :context
Expand Down
3 changes: 1 addition & 2 deletions app/views/application/layout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ def store_http_cookie(name, value:)
}
end

def store_signed_http_cookie(name, value:)
def store_signed_cookie(name, value:)
cookies.signed.permanent[name] = {
value:,
secure: App.production?,
httponly: true,
}
end

Expand Down
10 changes: 6 additions & 4 deletions test/models/current_user_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ class CurrentUserTest < ActiveSupport::TestCase

describe "#call" do
context "GIVEN a stored User Token" do
subject { unit_class.new(context: ContextDouble.new(user_token:)) }
subject {
unit_class.new(
context: ContextDouble.new(unit_class::COOKIE => user_token))
}

context "GIVEN a User exists for the User Token" do
let(:user_token) { user1.id }
Expand Down Expand Up @@ -50,9 +53,8 @@ def initialize(cookies = {})
@cookies = CookieJar.new(cookies)
end

def store_http_cookie(...) = nil
def store_signed_http_cookie(...) = nil
def user_agent = nil
def store_signed_cookie(...) = nil
def user_agent = "TEST_USER_AGENT"

attr_reader :cookies

Expand Down

0 comments on commit 4d885bf

Please sign in to comment.