mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
docs(realtime): realtime permissions (#50735)
All changes are related to Realtime permissions that I observed on support tickets recently: - Migrations can't have `ALTER TABLE realtime.messages`. That's is not allowed and breaks migrations. - Missing realtime.messages partitions are usually due to lack of connections - Errors like "must be owner of table messages" are misleading Closes REAL-1125 ## Additional context https://supabase.slack.com/archives/C01G8CC0X9D/p1789981286693829 and SU-479680 ## Checklist - [x] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) - [x] If I wrote a new docs topic or edited an existing topic, I used the `/write-the-docs` or `/edit-the-docs` skill, which references [WORD_LIST](https://github.com/supabase/supabase/blob/master/apps/docs/WORD_LIST.md) and the docs [CONTRIBUTING](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md) guide <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified Realtime authorization restrictions, supported RLS policy management, and the existing RLS configuration for `realtime.messages`. * Documented broadcast partition creation, connection requirements, and warning behavior when partitions are unavailable. * Added troubleshooting guidance for ownership errors, including migration rollback implications and supported remedies. * Added instructions for inspecting message partitions and diagnosing missing, expired, or unavailable partition configurations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
a21111b7e0
commit
c6b92060d2
4 files changed
+114
-2
No files matched your search
@@ -25,9 +25,17 @@ By creating RLS policies on the `realtime.messages` table you can control the ac
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Realtime locks down the `realtime` schema to protect it against unexpected changes to guarantee the healthy operation of the Realtime service and avoid conflicts that could be caused by future migrations. Creating a table or function in `realtime` is expected to fail with `permission denied for schema realtime`, whether you run the SQL yourself or through the dashboard. Managing RLS policies on `realtime.messages` is allowed.
|
||||
Realtime locks down the `realtime` schema to protect it against unexpected changes to guarantee the healthy operation of the Realtime service and avoid conflicts that could be caused by future migrations. Creating a table or function in `realtime` is expected to fail with `permission denied for schema realtime`, whether you run the SQL yourself or through the dashboard. Managing RLS policies on `realtime.messages` is allowed: [`supautils`](https://github.com/supabase/supautils) lets the `postgres` role run policy statements on that table without owning it.
|
||||
|
||||
Row level security (RLS) is enabled by default on table `realtime.messages`, you don't need to execute `ALTER TABLE realtime.messages ENABLE ROW LEVEL SECURITY`.
|
||||
Row Level Security is already enabled on `realtime.messages`, so don't add `ALTER TABLE realtime.messages ENABLE ROW LEVEL SECURITY` to a migration. Postgres checks table ownership before it checks whether the setting would change, so the statement fails with `42501 must be owner of table messages`. That aborts the transaction, and every statement after it is skipped, including any `create policy` statements that follow.
|
||||
|
||||
Remove the `ALTER TABLE` line and the rest of the migration passes. If a migration needs to check that RLS is on, read the catalog:
|
||||
|
||||
```sql
|
||||
select relrowsecurity from pg_class where oid = 'realtime.messages'::regclass;
|
||||
```
|
||||
|
||||
If you hit that error, see [must be owner of table messages](/docs/guides/troubleshooting/realtime-must-be-owner-of-table-messages).
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -1020,6 +1020,8 @@ Broadcast Changes allows you to trigger messages from your database. To achieve
|
||||
|
||||
It uses partitioned tables per day, which allows performant deletion of your previous messages by dropping the physical tables of this partitioned table. Tables older than 3 days are deleted.
|
||||
|
||||
A client connecting over WebSocket creates those daily partitions. `realtime.send` does not. With no partition for today, the insert fails and Postgres logs a `WarnSendingBroadcastMessage` warning, so connect a client before you broadcast from the database. See [Realtime: `WarnSendingBroadcastMessage`](/docs/guides/troubleshooting/realtime-warn-sending-broadcast-message) if you hit that warning.
|
||||
|
||||
Broadcasting from the database works like a client-side broadcast, using WebSockets to send JSON payloads. [Realtime Authorization](/docs/guides/realtime/authorization) is required and enabled by default to protect your data.
|
||||
|
||||
Broadcast Changes provides two functions to help you send messages:
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title = "Realtime: \"must be owner of table messages\" when setting up Authorization"
|
||||
topics = [ "realtime" ]
|
||||
keywords = [ "realtime.messages", "must be owner", "42501", "row level security", "policy", "authorization", "broadcast", "migration" ]
|
||||
|
||||
[[errors]]
|
||||
code = "42501"
|
||||
message = "must be owner of table messages"
|
||||
---
|
||||
|
||||
A migration that adds an RLS policy to `realtime.messages` fails:
|
||||
|
||||
```
|
||||
ERROR: 42501: must be owner of table messages
|
||||
```
|
||||
|
||||
## Cause
|
||||
|
||||
`realtime.messages` is owned by an internal role, `supabase_realtime_admin`. The `postgres` role is not a member of it and is not a superuser.
|
||||
|
||||
Policies still work because of [`supautils`](https://github.com/supabase/supautils), the extension that gives the `postgres` role its elevated permissions on a Supabase project. It lets `postgres` run `create policy`, `alter policy`, and `drop policy` on a fixed list of tables it does not own, and `realtime.messages` is on that list. The list covers policy statements only, so `ALTER TABLE` is still refused.
|
||||
|
||||
The failing statement is usually not the `create policy`. It's an `ALTER TABLE` earlier in the same migration:
|
||||
|
||||
```sql
|
||||
alter table realtime.messages enable row level security;
|
||||
alter table realtime.messages add column ...;
|
||||
alter table realtime.messages owner to postgres;
|
||||
```
|
||||
|
||||
The first one is the common case. RLS is already enabled on `realtime.messages`, and Postgres checks ownership before it checks whether the setting would change, so the statement fails on every project.
|
||||
|
||||
A failed statement aborts the transaction, so Postgres skips everything after it, including the `create policy`. `supabase db push` runs each migration file in one transaction, so the file rolls back and nothing is written to `supabase_migrations.schema_migrations`.
|
||||
|
||||
## Fix
|
||||
|
||||
Remove the `ALTER TABLE` line. Keep the policy:
|
||||
|
||||
```sql
|
||||
create policy "authenticated can read messages on room-1"
|
||||
on "realtime"."messages"
|
||||
for select
|
||||
to authenticated
|
||||
using (
|
||||
(select realtime.topic()) = 'room-1'
|
||||
);
|
||||
```
|
||||
|
||||
Run it from the SQL editor or with `supabase db push`. Both connect as `postgres`.
|
||||
|
||||
To check that RLS is on, read the catalog instead of setting it:
|
||||
|
||||
```sql
|
||||
select relrowsecurity from pg_class where oid = 'realtime.messages'::regclass;
|
||||
```
|
||||
|
||||
## What doesn't work
|
||||
|
||||
Granting the owning role fails, and support can't grant it either:
|
||||
|
||||
```sql
|
||||
grant supabase_realtime_admin to postgres;
|
||||
-- ERROR: 42501: "supabase_realtime_admin" role memberships are reserved, only superusers can grant them
|
||||
```
|
||||
|
||||
Restarting the project changes nothing.
|
||||
|
||||
Wrapping the `ALTER TABLE` in a function or a `do` block changes nothing either. The ownership check still runs, and a `security definer` function owned by `postgres` is still not the table owner.
|
||||
|
||||
## What postgres can do on realtime.messages
|
||||
|
||||
The `Yes` rows are what `supautils` delegates, plus the privileges granted to `postgres` directly.
|
||||
|
||||
| Statement | Allowed |
|
||||
| ----------------------------------------------------- | ------- |
|
||||
| `create policy`, `alter policy`, `drop policy` | Yes |
|
||||
| `comment on policy` | Yes |
|
||||
| `select`, `insert` | Yes |
|
||||
| `grant select`, `grant insert` to your own roles | Yes |
|
||||
| `alter table` in any form | No |
|
||||
| `drop table` | No |
|
||||
| Creating tables or functions in the `realtime` schema | No |
|
||||
|
||||
See [Realtime Authorization](/docs/guides/realtime/authorization) for writing the policies.
|
||||
@@ -45,6 +45,24 @@ Calling `realtime.send` or `realtime.send_binary`, the broadcast REST endpoint,
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Check which partitions you have
|
||||
|
||||
```sql
|
||||
select c.relname
|
||||
from pg_inherits i
|
||||
join pg_class c on c.oid = i.inhrelid
|
||||
where i.inhparent = 'realtime.messages'::regclass
|
||||
order by c.relname;
|
||||
```
|
||||
|
||||
The names are dates, so the result tells you which case you are in:
|
||||
|
||||
- One covers today. The missing partition is not the cause, so go to [When it is a real problem](#when-it-is-a-real-problem).
|
||||
- They all end on a past date. Your window lapsed. The newest name is three days after the last time partitions were created, because each creation covers yesterday through three days ahead.
|
||||
- No rows. No client has ever connected, so no partition was ever created.
|
||||
|
||||
Zero rows does not mean the Realtime migrations are missing. Those would fail this query with `42P01 relation "realtime.messages" does not exist`. If you see `42P01`, open a support ticket.
|
||||
|
||||
## How to avoid it
|
||||
|
||||
- Connect a client before broadcasting from the database. A live WebSocket connection both creates the partitions and starts the consumer that receives the message.
|
||||
|
||||
Reference in new issue
Block a user