diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb index d672697..c0cf330 100644 --- a/app/channels/application_cable/channel.rb +++ b/app/channels/application_cable/channel.rb @@ -1,4 +1,11 @@ module ApplicationCable class Channel < ActionCable::Channel::Base + # All current channels are public (Turbo::StreamsChannel for display pages). + # If an authenticated channel is added in the future, reject unauthorized + # connections in that channel's #subscribed method: + # + # def subscribed + # reject unless connection.current_shopkeeper + # end end end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index 0ff5442..3ea2751 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,4 +1,25 @@ module ApplicationCable class Connection < ActionCable::Connection::Base + identified_by :current_shopkeeper, :current_account + + def connect + self.current_shopkeeper = find_shopkeeper + self.current_account = find_account + end + + private + + def find_shopkeeper + env["warden"]&.user(:shopkeeper) + end + + # Extract the account UUID from the WebSocket upgrade request path, + # matching how AccountMiddleware sets the current account for HTTP + # requests. Display page URLs (display/shops/...) don't include an + # account UUID, so this returns nil for public connections. + def find_account + _, account_id, = request.path.split("/", 3) + Account.find_by(id: account_id) if AccountMiddleware::UUID_MATCHER.match?(account_id) + end end end diff --git a/app/views/display/shops/show.html.erb b/app/views/display/shops/show.html.erb index 2ce396e..2a5a0db 100644 --- a/app/views/display/shops/show.html.erb +++ b/app/views/display/shops/show.html.erb @@ -1,3 +1,4 @@ +<%# These streams are public by design — display pages are unauthenticated %> <%= turbo_stream_from @shop, :tb_stream_full_reload_entire_page %> <%= turbo_stream_from @shop, :tb_stream_update_item_tags %> diff --git a/test/channels/application_cable/connection_test.rb b/test/channels/application_cable/connection_test.rb index 800405f..8e31519 100644 --- a/test/channels/application_cable/connection_test.rb +++ b/test/channels/application_cable/connection_test.rb @@ -1,11 +1,35 @@ require "test_helper" class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase - # test "connects with cookies" do - # cookies.signed[:user_id] = 42 - # - # connect - # - # assert_equal connection.user_id, "42" - # end + test "anonymous connection succeeds with nil shopkeeper and account" do + connect + + assert_nil connection.current_shopkeeper + assert_nil connection.current_account + end + + test "authenticated connection identifies shopkeeper and account" do + shopkeeper = shopkeepers(:one) + account = shopkeeper.create_default_account + warden = Minitest::Mock.new + warden.expect(:user, shopkeeper, [:shopkeeper]) + + connect "/#{account.id}/cable", env: {"warden" => warden} + + assert_equal shopkeeper, connection.current_shopkeeper + assert_equal account, connection.current_account + warden.verify + end + + test "connection without account UUID in path has nil account" do + shopkeeper = shopkeepers(:one) + warden = Minitest::Mock.new + warden.expect(:user, shopkeeper, [:shopkeeper]) + + connect "/cable", env: {"warden" => warden} + + assert_equal shopkeeper, connection.current_shopkeeper + assert_nil connection.current_account + warden.verify + end end