realtime icon indicating copy to clipboard operation
realtime copied to clipboard

Getting CHANNEL_ERROR when trying to connect to private broadcast channel with a simple RLS policy

Open teddyhartanto opened this issue 1 year ago • 20 comments

Bug report

  • [x] I confirm this is a bug with Supabase, not with my own application.
  • [x] I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

I'm building an in-app chat for my application. Chat works fine when the channel is public. But, I can't seem to get it working when I tried making the channels private. Here's my frontend code:

// utils/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";

export const createClient = () =>
  createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
  );
// chat.tsx
"use client";

import { createClient } from "@/utils/supabase/client";
// truncated

export default function Chat(/* truncated */) {
  // truncated
  const supabase = createClient();
  React.useEffect(() => {
    supabase.auth.getSession().then((resp) => {
      supabase.realtime.setAuth(resp?.data?.session?.access_token || null);
      const ws = supabase.channel(chatRoomUuid, { config: { private: true } });
      ws.on("broadcast", { event: "message" }, ({ event, type, payload }) => {
        if (payload.userId !== user.id) {
          setMessages((messages) => [...messages, payload as Message]);
        }
      }).subscribe((state, err) => console.log("Sub ", state, err));
      return () => {
        ws.unsubscribe().then((resp) => console.log("Unsub ", resp));
      };
    });
  }, []);
  // truncated

I got the following log in the console:

Sub  CHANNEL_ERROR Error: "You do not have permissions to read from this Topic"
    at Object.eval [as callback] (RealtimeChannel.js:168:47)
<truncated>

My RLS: image

As you can see, it's a simple RLS. Even then, I'm getting a CHANNEL_ERROR. The connection would succeed if I remove FROM chat_room_members in the RLS policy. For some unknown reason, querying from any table in the policy fails the connection. What's happening?

I combed over docs, github discussions, github issues, and now Discord and I couldn't find anything. Am I missing something obvious here?

Expected behavior

SUBSCRIBED instead of CHANNEL_ERROR

System information

  • OS: Ubuntu 22.04
  • Browser: Chrome
  • Version of supabase/ssr: 0.4.0
  • Version of realtime-js: 2.10.2

teddyhartanto avatar Aug 02 '24 04:08 teddyhartanto

do you have other policies on chat_room_members table?

filipecabaco avatar Aug 02 '24 09:08 filipecabaco

Nope and RLS not enabled at all: image

I don't use RLS & Data API in my project (public schema)

Should I have enabled RLS and set a policy there too for Realtime Authz to work? I can try that.

teddyhartanto avatar Aug 02 '24 09:08 teddyhartanto

(I deleted the previous comment because I tested wrongly and so the result I reported was wrong. Below is the real report)

Okay I tried it out. image

I set the RLS real simple: allow all authenticated users to view chat_room_members image

I'm still getting the same CHANNEL_ERROR

teddyhartanto avatar Aug 02 '24 10:08 teddyhartanto

just for a sanity check, what happens if you have true on the realtime.messages table?

filipecabaco avatar Aug 02 '24 15:08 filipecabaco

i would get SUBSCRIBED instead of CHANNEL_ERROR if the RLS for realtime.messages is simply set to true.

I've tested from true -> EXIST (SELECT 1) -> (EXISTS ( SELECT 1 FROM chat_room_members)). It is the last one where the channel subscription gives me CHANNEL_ERROR state with an error Error: "You do not have permissions to read from this Topic". Therefore, it seems to be related to the FROM <table> clause

teddyhartanto avatar Aug 02 '24 15:08 teddyhartanto

interesting, can you try to do that query in supabase studio impersonating one of your users to see the output of the select?

filipecabaco avatar Aug 02 '24 20:08 filipecabaco

I've tested from true -> EXIST (SELECT 1) -> (EXISTS ( SELECT 1 FROM chat_room_members))

@teddyhartanto i was worried it might be an issue with the access token but if the first two are working then it's correct.

can you try the last one again but fully qualify the table name?

(EXISTS ( SELECT 1 FROM public.chat_room_members))

w3b6x9 avatar Aug 02 '24 20:08 w3b6x9

@w3b6x9 yeah i think it's not the access token. I monitored the WS payloads and I could see that the authenticated JWT token is passed correctly instead of an anon token on phx_join. I also tried logging out and in again.

I have also tried using the fully qualified table name, but it doesn't seem to matter. On save, Supabase Realtime would truncate the table name from public.chat_room_members -> chat_room_members. Furthermore, if the table name is wrong, Supabase Realtime would error on save.

Eg:

  (EXISTS ( SELECT 1 FROM non_existent))

image

teddyhartanto avatar Aug 03 '24 08:08 teddyhartanto

@filipecabaco

When RLS Policy is (EXISTS ( SELECT 1 FROM chat_room_members))

image

{"ref":"1","event":"phx_reply","payload":{"status":"error","response":{"reason":"You do not have permissions to read from this Topic"}},"topic":"realtime:19a51893-6168-4b2d-91a6-47b0db323f64"}	

When RLS Policy is (EXISTS ( SELECT 1))

Works fine. Seems like I can listen to the channel. image

Side note

I doubt it's related, but, there is a warning for postgres_changes in the studio (see screenshot). I inspected the WS payload and saw this in the payload config for phx_join:

postgres_changes: [{event: "*", schema: "public", table: "*"}]

Whereas in my app, the config is just

postgres_changes: []

teddyhartanto avatar Aug 03 '24 09:08 teddyhartanto

In Studio you can disable features by going to Filter messages and disabling postgres_changes

Regarding the policies, could you open a ticket in our support system? that will make it easier for me to understand what could be happening

filipecabaco avatar Aug 05 '24 08:08 filipecabaco

I am also seeing this error for private channels on local development. You do not have permissions to read from this Channel topic

I made the policy extremely permissive and I still get that message. When the channel is public it works fine

create policy "authenticated can read chat messages on user and business unit"
on "realtime"."messages"
as permissive
to authenticated
using (true)
with check (true);

taylorhakes avatar Mar 21 '25 05:03 taylorhakes

I too am experiencing this. Everything works fine on my hosted supabase instance, but it fails in local dev.

jscheel avatar Mar 28 '25 23:03 jscheel

Another observation: I'm pretty sure the client is only trying to subscribe with an anon role. I can look at the session data right before I subscribe, and it shows that I am authenticated. For testing, I did a simple anon policy with that just returns true. That works. If I set the policy to authenticated only, that will fail. If I set the policy to anon, and I try checking some rows in a RLS table, it fails.

Here is an example with an authenticated only policy that just returns true. I ensure the session is authenticated first, then try to subscribe:

Image

jscheel avatar Mar 29 '25 00:03 jscheel

Image

ERROR apps.supabase_home:_service.py:184 Authentication error: {'status': 403, 'message': ''} ERROR apps.supabase_home:_service.py:222 Authentication error being re-raised: Authentication error: {'status': 403, 'message': ''}

Wheen i try to edit it with sql it says the rls is already enabled

supabase_home.realtime:realtime.py:100 Authentication error during unsubscribe_all: Authentication error: {'status': 403, 'message': ''}. This may be due to missing RLS policies or insufficient permissions.

TechWithTy avatar Mar 29 '25 18:03 TechWithTy

Another observation: I'm pretty sure the client is only trying to subscribe with an anon role. I can look at the session data right before I subscribe, and it shows that I am authenticated. For testing, I did a simple anon policy with that just returns true. That works. If I set the policy to authenticated only, that will fail. If I set the policy to anon, and I try checking some rows in a RLS table, it fails.

I have the same problem, with my hosted instance, if I set the policy to public it works but when I set it to authenticated it doesn't work

Dishage avatar Mar 30 '25 13:03 Dishage

I have the same issue with locally installed instance for dev, one odd thing is if I keep hammering the connection (subscribe) it eventually connects. It is almost as if something internal is failing but works if you hit it at just the right time.

Tried all sorts of policies but if it sometimes works it must be something else.

Example of retrying until it connects (11 retries, 1 success):

Image

muteor avatar Mar 30 '25 18:03 muteor

Interesting discovery: on reconnections, the subscription actually succeeds.

To repro:

  1. Open page where you try to subscribe with the js client
  2. See permission error
  3. Do not close the page
  4. Kill the real-time container
  5. See the websocket drop
  6. Restart realtime
  7. Watch as the websocket reconnects and the subscription succeeds

jscheel avatar Mar 30 '25 19:03 jscheel

FWIW This seems to fix it for me:


  await client.realtime.setAuth(); // force auth, this stops you do not have permission errors
  const roomChannel = client.realtime.channel(room_id, {
    config: {
      private: true,
      broadcast: {
        ack: false,
        self: false,
      },
    },
  });

@jscheel Would be interesting to see if this fixes it for you too.

I am new to supabase and not very familiar with how the client works so not 100% sure if this is good or bad or why it seems to work, maybe someone more knowledgeable can figure it out.

muteor avatar Mar 30 '25 19:03 muteor

@muteor great catch, that works for me. So it seems likely that this is a problem with the client is not setting the realtime auth when it should.

jscheel avatar Mar 30 '25 20:03 jscheel

It looks like setAuth is only called on the heartbeat: https://github.com/supabase/realtime-js/blob/master/src/RealtimeClient.ts#L415, and if I'm reading this right, the heartbeat only happens every 25 seconds, unless it can set up a service worker, and then the service worker handles the keepAlive.

jscheel avatar Mar 30 '25 20:03 jscheel

Any updates or workarounds for this issue? I'm havving the same problem :/

bsmayer avatar Aug 21 '25 04:08 bsmayer

same issue here. any fixes?

link-sama avatar Sep 23 '25 04:09 link-sama

try upgrade node to v22 it solved my issue related to timeout connection

chaintng avatar Oct 14 '25 22:10 chaintng