[Question] Preventing unauthorized access to a resource's stream
mrrooijen opened this issue · 2 comments
Normally with ActionCable, as I understand it, if you want to prevent unauthorized access to a stream when someone attempts to subscribe to it, you simply check if the user has access to that particular resource, and if so, explicitly call stream_for
so that they can stream it.
For example:
class ChatMessageChannel < ApplicationCable::Channel
def subscribed
if chat = current_user.chats.find_by(id: params[:id])
# current_user has access to the chat,
# therefore we allow it to stream its data.
stream_for chat
else
# current_user doesn't have access to the chat,
# so we don't call `stream_for chat`, thus
# preventing the user from accessing the stream.
end
end
end
I'd like to know whether the following accomplishes the same thing in Motion:
class ChatComponent < ApplicationComponent
include Motion::Component
def initialize(id:, current_user:)
@messages = []
if chat = current_user.chats.find_by(id: id)
stream_for chat, :new_message
end
end
def new_message(msg)
@messages << msg
end
end
With this in place, is there any way that a user who doesn't have access to the chat resource can gain access to the stream regardless? I assume not, but perhaps I'm missing something. Any insight is much appreciated.
Really appreciate this library, thanks for making it open source!
@mrrooijen You have exactly the right idea! 🎉
Just like in ActionCable, only code on the server can call stream_from
/stream_for
and you are free to guard that with whatever application logic that you would like.
However, Motion takes this a step further by allowing you to to choose exactly what about each message is available to the client. Since only the rendered markup is exposed, anything that you do not explicitly choose to display will not be visible to the client. In fact, if you choose to ignore a message completely (by not changing the component's state at all in response to it), it isn't even possible for the client to observe that a broadcast was received:
def new_message(message)
return if message.hidden_from?(current_user)
@messages << message
end
Unrelated: You probably do not need to pass in the current_user
explicitly in this context.
Brilliant. Thanks for clarifying!
Unrelated: You probably do not need to pass in the current_user explicitly in this context.
Yeah, you probably don't even have to perform checks at this stage in many cases. The fact that you're rendering the component likely means that the current_user
has access to the resources in the first place.