How to use Pundit with Actioncable (Rails 5)

Viewed 669

I am wondering how to limit the connection to a channel or the streaming of messages over a channel in rails5. Currently I groups with users in the groups working with pundit and the connection to the websocket happens within that group. If a malicious user randomly guessed groups they could potentially read a message over a socket they shouldn't.

When you create a new message the following code is run in my controller:

if message.save
    ActionCable.server.broadcast(
        "messages_{message.groupchat_id}_channel",
        message: message.content,
        user: message.user.email
    )
    head :ok
end

I have no idea what I'm doing.

2 Answers

Here's the solution I found to use Pundit with Activecable.

First we need access to the user model. You can do that by following the instructions in the Action Cable Overview - Connection Setup. Mainly you need to change the code in connection.rb

# 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

Note: you may need to use a different way of finding your user. I'm using devise_token_auth and so needed to pass the uid, token, and client_id to the connection.rb and then got the user via this code:

if user && user.valid_token?(token, client_id)
  user
else
  reject_unauthorized_connection
end

I mention this just because how you get your user may vary. The main thing is that you need to use identified_by current_user and set it.

Another thing which I did not immediately find in the documentation, is that the current_user is now accessible by your channels. Since the user name may be different than your pundit_user name, I found it easiest to manually pass the user to Pundit at that point. So in subscribing with my channel file, I had this code:

def subscribed
  message = MessagePolicy::Scope.new(self.current_user, Project).resolve.find(params[:message])
  stream_for message
end

You could of course also manually authorize this way, instead of using Scope:

MessagePolicy.new(self.current_user, message).show?
Related