centrifugal / centrifuge-dart

Dart (Flutter) client SDK for bidirectional communication with Centrifugo and Centrifuge-based server over WebSocket
https://pub.dartlang.org/packages/centrifuge
MIT License
102 stars 29 forks source link

Unable to subscribe to a private channel #37

Closed andthereitgoes closed 4 years ago

andthereitgoes commented 4 years ago

After connecting I am trying to subscribe to a private channel such as $5daa0238-7f0c-11ea-b112-eae5569f8601#5daa0238-7f0c-11ea-b112-eae5569f8601

When I send the subscribe, on the server I am seeing the following in the log

{"level":"info","client":"436bb802-c9a3-471c-99d1-36b94cdf32c3","command":"id:2 method:SUBSCRIBE params:\"\\nJ$5daa0238-7f0c-11ea-b112-eae5569f8601#5daa0238-7f0c-11ea-b112-eae5569f8601\\022\\000\" ","error":"103: permission denied","reply":"id:2 error:<code:103 message:\"permission denied\" > ","user":"5daa0238-7f0c-11ea-b112-eae5569f8601","time":"2020-05-21T15:41:11Z","message":"client command error"}

I am sure this is related to protobuf format, but not sure what to do. The channel is being recieved as \\nJ$5daa0238-7f0c-11ea-b112-eae5569f8601#5daa0238-7f0c-11ea-b112-eae5569f8601\\022\\000\

I read in the readme for centrifuge-dart, that subscribe to private channels with JWT is supported. So not sure what I am missing. Is there any change required on the server?

@FZambia Sorry was not able to open the old issue.

FZambia commented 4 years ago

The channel is being recieved as \nJ$5daa0238-7f0c-11ea-b112-eae5569f8601#5daa0238-7f0c-11ea-b112-eae5569f8601\022\000\

This is just how things encoded in Protobuf - this does not mean Centrifugo received broken channel name.

In this case you need to provide private subscription token - see https://github.com/centrifugal/centrifuge-dart/blob/master/lib/src/client_config.dart#L16 and https://centrifugal.github.io/centrifugo/server/private_channels/

andthereitgoes commented 4 years ago

@FZambia Thanks for the answer. That's strange though (or maybe my misunderstanding) but I thought use of $ signifies a private channel. Since when there is a requirement of a separate JWT for private channels? Is this is a recent change?

I ask because we currently have centrifuge-js client version 2.0.0 and centrifugo server version 2.5.1 in production and we use a similar $5daa0238-7f0c-11ea-b112-eae5569f8601#5daa0238-7f0c-11ea-b112-eae5569f8601 private channel but don't provide a separate JWT token for the private channel and I have never had any issue.

FZambia commented 4 years ago

Since when there is a requirement of a separate JWT for private channels? Is this is a recent change?

Always worked this way, maybe you are using insecure mode in production which is not supposed to be on.

andthereitgoes commented 4 years ago

Hmm. This is our configuration. Have replaced the keys @FZambia

{
        "secret": "some_random_secret",
        "api_key": "some_random_api_key",
        "anonymous": false,
        "publish": false,
        "subscribe_to_publish": false,
        "presence": true,
        "join_leave": true,
        "history_size": 10,
        "history_lifetime": 300,
        "history_recover": true,
        "debug":false,
        "api_insecure":false,
        "log_level":"info"
}
FZambia commented 4 years ago

As you can see server rejects requests without proper subscription JWT. Show WebSocket frames generated by your Javascript app

andthereitgoes commented 4 years ago

What in my configuration is making it insecure? What do I need to add, remove or change?

andthereitgoes commented 4 years ago

Here are the frames from the JS App


{"params":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJUZXN0IE9yZ2FuaXNhdGlvbiAxIiwic3ViIjoiMWQ4ZTlhZTItOWYwNC0xMWU4LThhYjctNjZhMDM2NDMwMjg4IiwiZXhwIjoxNTkwMjU3MjYxfQ.gkYnSMSIQtpZuwspNmexI-Gboja-u7CTagzk--3VxYo"},"id":2} 
{"id":2,"result":{"client":"692373eb-49fd-4813-a0f6-64c15d69e648","version":"2.5.1","expires":true,"ttl":86400}} 
{"method":1,"params":{"channel":"$1d8e9ae2-9f04-11e8-8ab7-66a036430288#1d8e9ae2-9f04-11e8-8ab7-66a036430288","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJvcmdhbmlzYXRpb25fbmFtZV9nb2VzX2hlcmUiLCJzdWIiOiIxZDhlOWFlMi05ZjA0LTExZTgtOGFiNy02NmEwMzY0MzAyODgiLCJleHAiOjE1OTAyNTcyNjEsImNsaWVudCI6IjY5MjM3M2ViLTQ5ZmQtNDgxMy1hMGY2LTY0YzE1ZDY5ZTY0OCIsImNoYW5uZWwiOiIkMWQ4ZTlhZTItOWYwNC0xMWU4LThhYjctNjZhMDM2NDMwMjg4IzFkOGU5YWUyLTlmMDQtMTFlOC04YWI3LTY2YTAzNjQzMDI4OCJ9.datsctgGfdsXb-hY4qWnyYFlIwzMFdo6mSkhfr1nqxc"},"id":3}
{"id":3,"result":{"expires":true,"ttl":86400,"recoverable":true,"seq":60,"epoch":"LoyP","offset":60}} {"result":{"type":1,"channel":"$1d8e9ae2-9f04-11e8-8ab7-66a036430288#1d8e9ae2-9f04-11e8-8ab7-66a036430288","data":{"info":{"user":"1d8e9ae2-9f04-11e8-8ab7-66a036430288","client":"692373eb-49fd-4813-a0f6-64c15d69e648"}}}} 
{"method":7,"id":4} 
{"id":4}
{"method":7,"id":5}
{"id":5}
{"method":7,"id":6}
{"id":6}
FZambia commented 4 years ago

So look at this frame which represents subscribe command:

{"method":1,"params":{"channel":"$1d8e9ae2-9f04-11e8-8ab7-66a036430288#1d8e9ae2-9f04-11e8-8ab7-66a036430288","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJvcmdhbmlzYXRpb25fbmFtZV9nb2VzX2hlcmUiLCJzdWIiOiIxZDhlOWFlMi05ZjA0LTExZTgtOGFiNy02NmEwMzY0MzAyODgiLCJleHAiOjE1OTAyNTcyNjEsImNsaWVudCI6IjY5MjM3M2ViLTQ5ZmQtNDgxMy1hMGY2LTY0YzE1ZDY5ZTY0OCIsImNoYW5uZWwiOiIkMWQ4ZTlhZTItOWYwNC0xMWU4LThhYjctNjZhMDM2NDMwMjg4IzFkOGU5YWUyLTlmMDQtMTFlOC04YWI3LTY2YTAzNjQzMDI4OCJ9.datsctgGfdsXb-hY4qWnyYFlIwzMFdo6mSkhfr1nqxc"},"id":3}

As you can see it contains subscription token. So you already use it in Javascript. If you look at XHR tab of developer tools you will see an additional AJAX request for token being sent to your application backend by centrifuge-js.

What in my configuration is making it insecure? What do I need to add, remove or change?

It's ok, though since you posted some of your tokens here inside ws frames you may want to change your secret key.

andthereitgoes commented 4 years ago

Thanks @FZambia. Now I remembered (considering this was done quite some time back). In our JS client, when we initialise the client instance, we set the subscribeEndpoint and refreshEndpoint alongwith subscribeParams and refreshParams options.

The client uses the initial auth token to call this subscribe endpoint to acquire a new token which is then used for that particular channel. Correct me if I am wrong?

I couldn't find a similar setup in the dart client code - is customising the subscribe and refresh endpoint not possible in centrifuge-dart? In centrifuge-js it can be done https://github.com/centrifugal/centrifuge-js/blob/master/src/centrifuge.js#L102

FZambia commented 4 years ago

I couldn't find a similar setup in the dart client code

Yes, this library only provides a way to set your custom code to get subscription token, the implementation on how you will get it from your backend is up to you. PrivateSubEvent has all required fields to build token on server side.

BTW, since you are using user-limited channel (with #) then maybe you don't need to use private channels for your case at all. Private channel won't allow user to quickly reconnect if disabled in your app and connection token is still valid though.

andthereitgoes commented 4 years ago

this library only provides a way to set your custom code to get subscription token

Thanks @FZambia. Sorry, but I am not very experienced in dart so just want to run this by you. The flow in case of centrifuge-dart will be as follows

  1. create client with config of onPrivateSubCallback function like https://github.com/centrifugal/centrifuge-dart/blob/master/example/console/simple.dart#L20
  2. set auth token on the client
  3. connect
  4. call my own implementation to acquire subscription token (by calling an endpoint on my API)
  5. on success to acquire subscription token, send PrivateSubEvent ??
  6. then call client.getSubscription("\$1d8e9ae2-9f04-11e8-8ab7-66a036430288#1d8e9ae2-9f04-11e8-8ab7-66a036430288")? not sure from this point onwards?
final client = centrifuge.createClient(
      url,
      config: centrifuge.ClientConfig(
          headers: <String, dynamic>{'user-id': 42, 'user-name': 'The Answer'},
          onPrivateSub: (centrifuge.PrivateSubEvent event) {
            return Future.value('<SUBSCRIPTION JWT>');
          }),
    );

    client.connectStream.listen(onEvent);
    client.disconnectStream.listen(onEvent);
client.setToken('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0c3VpdGVfand0In0.hPmHsVqvtY88PvK4EmJlcdwNuKFuy3BGaF7dMaKdPlw');
    client.connect();

// FETCH PRIVATE SUBSCRIPTION TOKEN HERE using client_id and channel

    final subscription = client.getSubscription('\$1d8e9ae2-9f04-11e8-8ab7-66a036430288#1d8e9ae2-9f04-11e8-8ab7-66a036430288');
FZambia commented 4 years ago

Your onPrivateSub callback should be automatically called when required, you should put the code fetching subscription token inside callback.

andthereitgoes commented 4 years ago

Thanks @FZambia. So the modified sequence will be

  1. create client with config of onPrivateSubCallback function like https://github.com/centrifugal/centrifuge-dart/blob/master/example/console/simple.dart#L20
  2. set auth token on the client
  3. connect
  4. client.getSubscription("\$1d8e9ae2-9f04-11e8-8ab7-66a036430288#1d8e9ae2-9f04-11e8-8ab7-66a036430288")
  5. This will automatically call onPrivateSub fetch the token and return the Future.

Quick question - when will the server receive the newly fetched subscription token from point 5?

FZambia commented 4 years ago

Yes, this sequence looks fine.

when will the server receive the newly fetched subscription token from point 5?

Do you mean Centrifugo server? If yes - it will receive newly fetched subscription token with every subscribe command from client - this can be initial subscribe or every other subscribe after (automatic reconnect for example). So before sending subscribe request Dart client fetches token using callback you provided, then attaches fetched token to subscribe command flying to Centrifugo.

andthereitgoes commented 4 years ago

Got it. Thanks @FZambia for your help and time. I will try this tomorrow and close this ticket if it all works as expected.