-
Notifications
You must be signed in to change notification settings - Fork 22k
Description
Steps to reproduce
- Create
app/channels/application_cable/connection.rb
as a copy of section 3.1.1 of the Action Cable docs
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
- Create
test/fixtures/users.yml
to contain the following:
lou:
id: 1
email: person@example.org
- Create a test case in
test/channels/application_cable/connection_test.rb
modeled after the basic example in Action Cable Connection TestCase docs
require "test_helper"
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
test "connects with cookies" do
cookies.encrypted["user_id"] = 1
connect
assert_equal 1, connection.current_user.id
end
end
-
Run
./bin/rails test
, tests pass! ✅ -
Change the way the cookie is set in step 3, swapping from the shorthand to longhand method of setting a cookie, per the ActionDispatch docs:
- cookies.encrypted["user_id"] = 1
+ cookies.encrypted["user_id"] = { value: 1, expires: 1.hour }
- Run
./bin/rails test
again to observe a failing test 🛑
Executable repro
I modified this from the executable test case templates to support ActionCable. If all goes well you should be able to run with ruby failing_case.rb
and see one passing test and one failing test. The second test fails on both 7.1.3.3 and 8.0.0.alpha.
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
gem "rails"
# If you want to test against edge Rails replace the previous line with this:
# gem "rails", github: "rails/rails", branch: "main"
end
require "action_controller/railtie"
require "action_cable"
require "active_support/time"
class TestApp < Rails::Application
config.root = __dir__
config.hosts << "example.org"
config.secret_key_base = "secret_key_base"
config.logger = Logger.new($stdout)
Rails.logger = config.logger
end
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user_id
def connect
self.current_user_id = cookies.encrypted[:user_id]
end
end
end
require "minitest/autorun"
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
tests ApplicationCable::Connection
test "passing test - connects with cookies" do
cookies.encrypted["user_id"] = 1
connect
assert_equal 1, connection.current_user_id
end
test "failing test - connects with cookies" do
cookies.encrypted["user_id"] = { value: 1, expires: 1.hour }
connect
assert_equal 1, connection.current_user_id
end
end
Expected behavior
ActionCable tests that rely on encrypted cookies should pass regardless of which cookie-setting syntax is used:
cookies.encrypted[:hello] = "world"
# or
cookies.encrypted[:hello] = { value: "world", expires: 1.hour }
Actual behavior
ActionCable tests that rely on encrypted cookies fail if the developer uses the longhand form to set a cookie (which allows us to set expires
, httponly
, secure
, or same_site
options):
cookies.encrypted[:hello] = "world" # ✅
cookies.encrypted[:hello] = { value: "world", expires: 1.hour } # 🛑
System configuration
Rails version: Rails 7.1.3.3
Ruby version: ruby 3.2.2