stimulusreflex / stimulus_reflex

Build reactive applications with the Rails tooling you already know and love.
https://docs.stimulusreflex.com
MIT License
2.28k stars 172 forks source link

Authorization #292

Closed ghost closed 4 years ago

ghost commented 4 years ago

Thank you for making this! I see it's potential. However, I primarily build multi-user applications. How do I keep one user from seeing another user's updates? I tried uniquely identifying each element and that seems to work except it throws js errors for other users saying it can't find the elements. And is there really no way to authorize requests using Pundit and/or Rolify?

Just looking for some guidance in this regard. I tried other searches but this library is so new I mostly get references back to here or the reflex website, other than health resources pointing to stimulate/reflex articles :)

romanos commented 4 years ago

Hi @andersonsystems-deploy, I'm new to SR as well, but I haven't run into this problem yet. Are you seeing this with logged-in users or anonymous visitors? My users are all logged-in and authenticated, so I can't replicate your issue (though I immediately fired up a few browser windows in a panic). I followed the Authentication/Authorization guide in the docs: https://docs.stimulusreflex.com/authentication and had no issues so far. For what it's worth, we're using Devise and CanCanCan to manage this.

cpanderson commented 4 years ago

It depends what you're doing I guess. At the top of the Authentication page they have this warning...

"However, the moment you deploy to a host with more than one person accessing your app, you'll find that you're sharing a session and seeing other people's updates"

So, as I understand it, if you're not uniquely identifying the elements with a user id, when you update html on those elements, cable-ready will update the same element for every user that is currently on the same browser page.

leastbad commented 4 years ago

Hi folks! Sorry for the late reply in an interesting conversation. I hope that I can provide some answers.

Christopher, I have confidence that you know what you're trying to do in your head, but outwardly you are confusing two concepts: authentication and authorization.

It's true that out of the box, if you don't configure an authentication scheme (whether it's session id, Devise or something else) your users will all receive the same updates. For this reason, one of the first things people do is tie their authentication to the most appropriate mechanism. I would consider authentication well supported in SR, and I think we've struck a good balance on flexibility vs. omakase.

Authentication just means your browser can connect to the ActionCable endpoint and you won't be disconnected. Once you're connected, the client is free to subscribe to any combination of channels unless you have a plan for managing authorization, which controls access to individual resources and/or operations.

I like and use Rolify after all these years - including with SR - but Rolify itself doesn't have any teeth; it provides a convenient mechanism for attaching role-based metadata, but it doesn't attempt to protect anything on its own.

Unfortunately, most tools people use to bounce people "not on the list" are generally designed to guard against running controller actions. In the case of a Reflex, the damage can be done before the controller action runs... and if you're talking about a selector or nothing morph, no controller action exists to be guarded. This means that you have to approach authorizing Reflex actions differently.

What most folks seem to do is perform a permissions check in the server-side before_reflex callback, and if the user doesn't have permission, you can cancel the Reflex by calling throw :abort in your callback method. Depending on how your authorization system works (I'm not familiar with CanCanCan, for example) it might be possible to use it in this manner, but possibly not. There's not much we can do about that, unless those tools decide to adopt SR as a supported resource. That would be amazing.

Soon, another possibility will hopefully be on the table: https://docs.stimulusreflex.com/authentication#token-based-authentication describes using tokens but the same mechanism could be used to bounce people from subscribing to channels based on permissions as well. That would be super cool to see working. Unfortunately, the code required to make this happen still needs to be approved. Please feel free to go +1 #243 if you'd like to see this happen sooner.

Does this help, Christopher?

cpanderson commented 4 years ago

I do the know the difference between authorization and authentication and I appreciate your explanation. I will just have to stick with plain Stimulus for now. Thanks!

leastbad commented 4 years ago

Okay.

Could you go into a bit more detail as to how you made this decision? Was I not able to answer some aspect of your question? Stimulus and StimulusReflex are similar in name alone, so your comment is confusing.

Your initial question and follow-up asked for clarification to make sure that each user saw the updates intended for them. I want to make sure you understand that when you said

So, as I understand it, if you're not uniquely identifying the elements with a user id, when you update html on those elements, cable-ready will update the same element for every user that is currently on the same browser page.

...that you were understanding wrong. 😁 You do not have to uniquely identify elements with a user id or anything like that.

cpanderson commented 4 years ago

As per my initial request above I want to keep users from seeing each other's updates. In some cases, like in a chat feature, I would want this but not in most cases. What I found when having 2 users logged in from different browsers is when I use reflex to change html on an element, both users saw the change. If I uniquely identify the element then this doesn't occur but throws a js error for all other users because it can't find the element.

leastbad commented 4 years ago

I understand. Let's figure out why this isn't happening for you. This should be table stakes.

Are you on Discord? It will be much easier to assist in realtime.

cpanderson commented 4 years ago

I did exactly as the Authentication page says to do for Sorcery but it doesn't work. I still get updates for all authenticated users. However, I can confirm this works to get the user_id...

User.find_by(id: request.session.fetch("user_id", nil)

but I had to do that in the view controller to confirm because for some reason I can't get any logger/puts statements out of connection.rb.

It also states this on the Useful Patterns Page for The Current Pattern...

class ApplicationReflex < StimulusReflex::Reflex
  delegate :current_user, to: :connection

  before_reflex do
    Current.user = current_user
  end
end

However, when I try that I get...

[Error] Unhandled Promise Rejection: StimulusReflex::Channel Failed to invoke .... uninitialized constant ApplicationReflex::Current

leastbad commented 4 years ago

Please feel free to post a link to a Github repo that reproduces this behaviour, and I will send back a patch with it working.

What you are describing sounds annoying and i am sorry that you're having trouble. It's probably something 🤦‍♂️. If you meet me halfway, I will get you up and running.