chore: made slack readme policy the full schema to reduce confusion

This commit is contained in:
Ant Wilson authored and GitHub committed 2021-06-08 11:27:37 +08:00
1 parent 0c6044d197
commit 71ae44029a
1 file changed
+153 -51
+153 -51
View File
@@ -135,68 +135,170 @@ When you start a Postgres database on Supabase, we populate it with an `auth` sc
When a user logs in, they are issued a JWT with the role `authenticated` and their UUID.
We can use these details to provide fine-grained control over what each user can and cannot do.
This is a trimmed-down schema, with the policies:
Full schema here with role-based access control:
```sql
--
-- For use with https://github.com/supabase/supabase/tree/master/examples/nextjs-slack-clone
--
-- Custom types
create type public.app_permission as enum ('channels.delete', 'messages.delete');
create type public.app_role as enum ('admin', 'moderator');
create type public.user_status as enum ('ONLINE', 'OFFLINE');
-- USERS
create table public.users (
id uuid not null primary key, -- UUID from auth.users
username text,
status user_status default 'OFFLINE'::public.user_status
);
comment on table public.users is 'Profile data for each user.';
comment on column public.users.id is 'References the internal Supabase Auth user.';
-- CHANNELS
create table public.channels (
id bigint generated by default as identity primary key,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null,
slug text not null unique,
created_by uuid references public.users not null
);
comment on table public.channels is 'Topics and groups.';
-- MESSAGES
create table public.messages (
id bigint generated by default as identity primary key,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null,
message text,
user_id uuid references public.users not null,
channel_id bigint references public.channels on delete cascade not null
);
comment on table public.messages is 'Individual messages sent by each user.';
-- USER ROLES
create table public.user_roles (
user_id uuid references public.users on delete cascade not null,
role app_role not null,
unique (user_id, role)
);
comment on table public.user_roles is 'Application roles for each user.';
-- ROLE PERMISSIONS
create table public.role_permissions (
role app_role not null,
permission app_permission not null,
unique (role, permission)
);
comment on table public.role_permissions is 'Application permissions for each role.';
-- authorize with role-based access control (RBAC)
CREATE FUNCTION public.authorize(
create function public.authorize(
requested_permission app_permission,
user_id uuid
)
RETURNS BOOLEAN AS $$
DECLARE
returns boolean as $$
declare
bind_permissions int;
BEGIN
SELECT count(*)
FROM public.role_permissions
INNER JOIN public.user_roles on role_permissions.role = user_roles.role
WHERE role_permissions.permission = authorize.requested_permission
AND user_roles.user_id = authorize.user_id
INTO bind_permissions;
begin
select count(*)
from public.role_permissions
inner join public.user_roles on role_permissions.role = user_roles.role
where role_permissions.permission = authorize.requested_permission
and user_roles.user_id = authorize.user_id
into bind_permissions;
return bind_permissions > 0;
end;
$$ language plpgsql security definer;
RETURN bind_permissions > 0;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Secure the tables
alter table public.users enable row level security;
alter table public.channels enable row level security;
alter table public.messages enable row level security;
alter table public.user_roles enable row level security;
alter table public.role_permissions enable row level security;
create policy "Allow logged-in read access" on public.users for select using ( auth.role() = 'authenticated' );
create policy "Allow individual insert access" on public.users for insert with check ( auth.uid() = id );
create policy "Allow individual update access" on public.users for update using ( auth.uid() = id );
create policy "Allow logged-in read access" on public.channels for select using ( auth.role() = 'authenticated' );
create policy "Allow individual insert access" on public.channels for insert with check ( auth.uid() = created_by );
create policy "Allow individual delete access" on public.channels for delete using ( auth.uid() = created_by );
create policy "Allow authorized delete access" on public.channels for delete using ( authorize('channels.delete', auth.uid()) );
create policy "Allow logged-in read access" on public.messages for select using ( auth.role() = 'authenticated' );
create policy "Allow individual insert access" on public.messages for insert with check ( auth.uid() = user_id );
create policy "Allow individual update access" on public.messages for update using ( auth.uid() = user_id );
create policy "Allow individual delete access" on public.messages for delete using ( auth.uid() = user_id );
create policy "Allow authorized delete access" on public.messages for delete using ( authorize('messages.delete', auth.uid()) );
create policy "Allow individual read access" on public.user_roles for select using ( auth.uid() = user_id );
-- USER PROFILES
CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE');
CREATE TABLE public.users (
id uuid NOT NULL PRIMARY KEY, -- UUID from auth.users (Supabase)
username text,
status user_status DEFAULT 'OFFLINE'::public.user_status
);
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in read access" on public.users FOR SELECT USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.users FOR INSERT WITH CHECK ( auth.uid() = id );
CREATE POLICY "Allow individual update access" on public.users FOR UPDATE USING ( auth.uid() = id );
-- Send "previous data" on change
alter table public.users replica identity full;
alter table public.channels replica identity full;
alter table public.messages replica identity full;
-- CHANNELS
CREATE TABLE public.channels (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
slug text NOT NULL UNIQUE
);
ALTER TABLE public.channels ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in read access" on public.channels FOR SELECT USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.channels FOR INSERT WITH CHECK ( auth.uid() = created_by );
CREATE POLICY "Allow individual update access" on public.channels FOR UPDATE USING ( auth.uid() = created_by );
CREATE POLICY "Allow individual delete access" on public.channels FOR DELETE USING ( auth.uid() = created_by );
CREATE POLICY "Allow authorized delete access" on public.channels FOR DELETE USING ( authorize('channels.delete', auth.uid()) );
-- inserts a row into public.users and assigns roles
create function public.handle_new_user()
returns trigger as $$
declare is_admin boolean;
begin
insert into public.users (id, username)
values (new.id, new.email);
select count(*) = 1 from auth.users into is_admin;
if position('+supaadmin@' in new.email) > 0 then
insert into public.user_roles (user_id, role) values (new.id, 'admin');
elsif position('+supamod@' in new.email) > 0 then
insert into public.user_roles (user_id, role) values (new.id, 'moderator');
end if;
return new;
end;
$$ language plpgsql security definer;
-- MESSAGES
CREATE TABLE public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
message text,
user_id uuid REFERENCES public.users NOT NULL,
channel_id bigint REFERENCES public.channels NOT NULL
);
ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in read access" on public.messages FOR SELECT USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.messages FOR INSERT WITH CHECK ( auth.uid() = user_id );
CREATE POLICY "Allow individual update access" on public.messages FOR UPDATE USING ( auth.uid() = user_id );
CREATE POLICY "Allow individual delete access" on public.messages FOR DELETE USING ( auth.uid() = user_id );
CREATE POLICY "Allow authorized delete access" on public.messages FOR DELETE USING ( authorize('messages.delete', auth.uid()) );
-- trigger the function every time a user is created
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
/**
* REALTIME SUBSCRIPTIONS
* Only allow realtime listening on public tables.
*/
begin;
-- remove the realtime publication
drop publication if exists supabase_realtime;
-- re-create the publication but don't enable it for any tables
create publication supabase_realtime;
commit;
-- add tables to the publication
alter publication supabase_realtime add table public.channels;
alter publication supabase_realtime add table public.messages;
alter publication supabase_realtime add table public.users;
-- DUMMY DATA
insert into public.users (id, username)
values
('8d0fd2b3-9ca7-4d9e-a95f-9e13dded323e', 'supabot');
insert into public.channels (slug, created_by)
values
('public', '8d0fd2b3-9ca7-4d9e-a95f-9e13dded323e'),
('random', '8d0fd2b3-9ca7-4d9e-a95f-9e13dded323e');
insert into public.messages (message, channel_id, user_id)
values
('Hello World 👋', 1, '8d0fd2b3-9ca7-4d9e-a95f-9e13dded323e'),
('Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.', 2, '8d0fd2b3-9ca7-4d9e-a95f-9e13dded323e');
insert into public.role_permissions (role, permission)
values
('admin', 'channels.delete'),
('admin', 'messages.delete'),
('moderator', 'messages.delete');
```
## Authors