diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index b533e6779b9..3f3f3cf9754 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -607,6 +607,10 @@ export const auth = {
name: 'Column Level Security',
url: '/guides/auth/column-level-security',
},
+ {
+ name: 'Custom Claims & RBAC',
+ url: '/guides/auth/custom-claims-and-role-based-access-control-rbac',
+ },
],
},
{
diff --git a/apps/docs/content/guides/auth/auth-hooks.mdx b/apps/docs/content/guides/auth/auth-hooks.mdx
index f094433bcd6..c6a833b30fc 100644
--- a/apps/docs/content/guides/auth/auth-hooks.mdx
+++ b/apps/docs/content/guides/auth/auth-hooks.mdx
@@ -572,7 +572,7 @@ as $$
is_admin boolean;
begin
-- Check if the user is marked as admin in the profiles table
- select is_admin into is_admin from profiles where user_id = event->>'user_id'::uuid;
+ select is_admin into is_admin from profiles where user_id = (event->>'user_id')::uuid;
-- Proceed only if the user is an admin
if is_admin then
diff --git a/apps/docs/content/guides/auth/custom-claims-and-role-based-access-control-rbac.mdx b/apps/docs/content/guides/auth/custom-claims-and-role-based-access-control-rbac.mdx
new file mode 100644
index 00000000000..b1f22158f9b
--- /dev/null
+++ b/apps/docs/content/guides/auth/custom-claims-and-role-based-access-control-rbac.mdx
@@ -0,0 +1,278 @@
+---
+id: 'custom-claims-and-role-based-access-control-rbac'
+title: 'Custom Claims & Role-based Access Control (RBAC)'
+description: 'Use Auth Hooks to add custom claims for managing role-based access control.'
+---
+
+Custom Claims are special attributes attached to a user that you can use to control access to portions of your application. For example:
+
+```json
+{
+ "user_role": "admin",
+ "plan": "TRIAL",
+ "user_level": 100,
+ "group_name": "Super Guild!",
+ "joined_on": "2022-05-20T14:28:18.217Z",
+ "group_manager": false,
+ "items": ["toothpick", "string", "ring"]
+}
+```
+
+To implement Role-Based Access Control (RBAC) with `custom claims`, use a [Custom Access Token Auth Hook](/guides/auth/auth-hooks#hook-custom-access-token). This hook runs before a token is issued. You can use it to add additional claims to the user's JWT.
+
+This guide uses the [Slack Clone example](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone) to demonstrate how to add a `user_role` claim and use it in your [Row Level Security (RLS) policies](/guides/auth/row-level-security).
+
+## Create a table to track user roles and permissions
+
+In this example, you will implement two user roles with specific permissions:
+
+- `moderator`: A moderator can delete all messages but not channels.
+- `admin`: An admin can delete all messages and channels.
+
+```sql supabase/migrations/init.sql
+-- Custom types
+create type public.app_permission as enum ('channels.delete', 'messages.delete');
+create type public.app_role as enum ('admin', 'moderator');
+
+-- USER ROLES
+create table public.user_roles (
+ id bigint generated by default as identity primary key,
+ 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 (
+ id bigint generated by default as identity primary key,
+ 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.';
+```
+
+
+
+ For the [full schema](https://github.com/supabase/supabase/blob/master/examples/slack-clone/nextjs-slack-clone/README.md), see the example application on [GitHub](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone).
+
+
+
+You can now manage your roles and permissions in SQL. For example, to add the mentioned roles and permissions from above, run:
+
+```sql supabase/seed.sql
+insert into public.role_permissions (role, permission)
+values
+ ('admin', 'channels.delete'),
+ ('admin', 'messages.delete'),
+ ('moderator', 'messages.delete');
+```
+
+## Create Auth Hook to apply user role
+
+The [Custom Access Token Auth Hook](/guides/auth/auth-hooks#hook-custom-access-token) runs before a token is issued. You can use it edit the JWT.
+
+
+
+
+```sql supabase/migrations/auth_hook.sql
+-- Create the auth hook function
+create or replace function public.custom_access_token_hook(event jsonb)
+returns jsonb
+language plpgsql
+immutable
+as $$
+ declare
+ claims jsonb;
+ user_role public.app_role;
+ begin
+ -- Check if the user is marked as admin in the profiles table
+ select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid;
+
+ claims := event->'claims';
+
+ if user_role is not null then
+ -- Set the claim
+ claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
+ else
+ claims := jsonb_set(claims, '{user_role}', 'null');
+ end if;
+
+ -- Update the 'claims' object in the original event
+ event := jsonb_set(event, '{claims}', claims);
+
+ -- Return the modified or original event
+ return event;
+ end;
+$$;
+
+grant usage on schema public to supabase_auth_admin;
+
+grant execute
+ on function public.custom_access_token_hook
+ to supabase_auth_admin;
+
+revoke execute
+ on function public.custom_access_token_hook
+ from authenticated, anon;
+
+grant all
+ on table public.user_roles
+to supabase_auth_admin;
+
+revoke all
+ on table public.user_roles
+ from authenticated, anon;
+
+create policy "Allow auth admin to read user roles" ON public.user_roles
+as permissive for select
+to supabase_auth_admin
+using (true)
+```
+
+
+
+
+```sql supabase/migrations/auth_hook.sql
+-- Enable the "plv8" extension
+create extension plv8;
+
+-- Create the auth hook function
+create or replace function custom_access_token_hook(event jsonb)
+returns jsonb
+language plv8
+as $$
+ var user_role;
+
+ // Fetch the current user's user_role from the public user_roles table.
+ var result = plv8.execute("select role from public.user_roles where user_id = $1", [event.user_id]);
+ if (result.length > 0) {
+ user_role = result[0].role;
+ } else {
+ // Assign null
+ user_role = null;
+ }
+
+ // Check if 'claims' exists in the event object; if not, initialize it
+ if (!event.claims) {
+ event.claims = {};
+ }
+
+ // Update the level in the claims
+ event.claims.user_role = user_role;
+
+ return event;
+$$;
+
+grant usage on schema public to supabase_auth_admin;
+
+grant execute
+ on function public.custom_access_token_hook
+ to supabase_auth_admin;
+
+revoke execute
+ on function public.custom_access_token_hook
+ from authenticated, anon;
+
+grant all
+ on table public.user_roles
+to supabase_auth_admin;
+
+revoke all
+ on table public.user_roles
+ from authenticated, anon;
+
+create policy "Allow auth admin to read user roles" ON public.user_roles
+as permissive for select
+to supabase_auth_admin
+using (true)
+```
+
+
+
+
+### Enable the hook
+
+In the dashboard, navigate to [`Authentication > Hooks (Beta)`](/dashboard/project/_/auth/hooks) and select the appropriate PostgreSQL function from the dropdown menu.
+
+When developing locally, follow the [local development](/guides/auth/auth-hooks#local-development) instructions.
+
+
+
+To learn more about Auth Hooks, see the [Auth Hooks docs](/guides/auth/auth-hooks).
+
+
+
+## Accessing custom claims in RLS policies
+
+To utilize Role-Based Access Control (RBAC) in Row Level Security (RLS) policies, create an `authorize` method that reads the user's role from their JWT and checks the role's permissions:
+
+```sql supabase/migrations/init.sql
+create function public.authorize(
+ requested_permission app_permission,
+ user_id uuid
+)
+returns boolean as $$
+declare
+ bind_permissions int;
+begin
+ select count(*)
+ from public.role_permissions
+ where role_permissions.permission = authorize.requested_permission
+ and role_permissions.role = (auth.jwt() ->> 'user_role')::public.app_role
+ into bind_permissions;
+
+ return bind_permissions > 0;
+end;
+$$ language plpgsql security definer set search_path = public;
+```
+
+
+
+You can read more about using functions in RLS policies in the [RLS guide](/guides/auth/row-level-security#using-functions).
+
+
+
+You can then use the `authorize` method within your RLS policies. For example, to enable the desired delete access, you would add the following policies:
+
+```sql
+create policy "Allow authorized delete access" on public.channels for delete using ( authorize('channels.delete', auth.uid()) );
+create policy "Allow authorized delete access" on public.messages for delete using ( authorize('messages.delete', auth.uid()) );
+```
+
+## Accessing custom claims in your application
+
+The auth hook will only modify the access token JWT but not the auth response. Therefore, to access the custom claims in your application, e.g. your browser client, or server-side middleware, you will need to decode the `access_token` JWT on the auth session.
+
+In a JavaScript client application you can for example use the [`jwt-decode` package](https://www.npmjs.com/package/jwt-decode):
+
+```js
+import { jwtDecode } from 'jwt-decode'
+
+const { subscription: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
+ if (session) {
+ const jwt = jwtDecode(session.access_token)
+ const userRole = jwt.user_role
+ }
+})
+```
+
+For server-side logic you can use packages like [express-jwt](https://github.com/auth0/express-jwt), [koa-jwt](https://github.com/stiang/koa-jwt), [PyJWT](https://github.com/jpadilla/pyjwt), [dart_jsonwebtoken](https://pub.dev/packages/dart_jsonwebtoken), [Microsoft.AspNetCore.Authentication.JwtBearer](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer), etc.
+
+## Conclusion
+
+You now have a robust system in place to manage user roles and permissions within your database that automatically propagates to Supabase Auth.
+
+## More resources
+
+- [Auth Hooks](/guides/auth/auth-hooks)
+- [Row Level Security](/guides/auth/row-level-security)
+- [RLS Functions](/guides/auth/row-level-security#using-functions)
+- [Next.js Slack Clone Example](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone)
diff --git a/examples/slack-clone/nextjs-slack-clone/README.md b/examples/slack-clone/nextjs-slack-clone/README.md
index 6041b6b84ea..183701a7358 100644
--- a/examples/slack-clone/nextjs-slack-clone/README.md
+++ b/examples/slack-clone/nextjs-slack-clone/README.md
@@ -115,173 +115,8 @@ 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.
-Full schema here with role-based access control:
-
-```sql
---
--- For use with https://github.com/supabase/supabase/tree/master/examples/slack-clone/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 (
- id bigint generated by default as identity primary key,
- 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 (
- id bigint generated by default as identity primary key,
- 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(
- requested_permission app_permission,
- user_id uuid
-)
-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;
-
- 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 );
-
--- 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;
-
--- 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;
-
--- 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');
-```
+- For the full schema refer to [full-schema.sql](./full-schema.sql).
+- For documentation on Role-based Access Control, refer to the [docs](https://supabase.com/docs/guides/auth/custom-claims-and-role-based-access-control-rbac).
## Authors
diff --git a/examples/slack-clone/nextjs-slack-clone/components/Layout.js b/examples/slack-clone/nextjs-slack-clone/components/Layout.js
index a592e0d83be..46da4ce13dc 100644
--- a/examples/slack-clone/nextjs-slack-clone/components/Layout.js
+++ b/examples/slack-clone/nextjs-slack-clone/components/Layout.js
@@ -5,7 +5,7 @@ import { addChannel, deleteChannel } from '~/lib/Store'
import TrashIcon from '~/components/TrashIcon'
export default function Layout(props) {
- const { signOut, user, userRoles } = useContext(UserContext)
+ const { signOut, user } = useContext(UserContext)
const slugify = (text) => {
return text
@@ -60,7 +60,6 @@ export default function Layout(props) {
key={x.id}
isActiveChannel={x.id === props.activeChannelId}
user={user}
- userRoles={userRoles}
/>
))}
@@ -73,13 +72,13 @@ export default function Layout(props) {
)
}
-const SidebarItem = ({ channel, isActiveChannel, user, userRoles }) => (
+const SidebarItem = ({ channel, isActiveChannel, user }) => (
<>
{channel.slug}
- {channel.id !== 1 && (channel.created_by === user?.id || userRoles.includes('admin')) && (
+ {channel.id !== 1 && (channel.created_by === user?.id || user?.appRole === 'admin') && (
deleteChannel(channel.id)}>
diff --git a/examples/slack-clone/nextjs-slack-clone/components/Message.js b/examples/slack-clone/nextjs-slack-clone/components/Message.js
index 201012e089d..825bbca39aa 100644
--- a/examples/slack-clone/nextjs-slack-clone/components/Message.js
+++ b/examples/slack-clone/nextjs-slack-clone/components/Message.js
@@ -4,20 +4,19 @@ import { deleteMessage } from '~/lib/Store'
import TrashIcon from '~/components/TrashIcon'
const Message = ({ message }) => {
- const { user, userRoles } = useContext(UserContext)
+ const { user } = useContext(UserContext)
return (
- {(user?.id === message.user_id ||
- userRoles.some((role) => ['admin', 'moderator'].includes(role))) && (
+ {(user?.id === message.user_id || ['admin', 'moderator'].includes(user?.appRole)) && (
deleteMessage(message.id)}>
)}
-
{message.author.username}
+
{message?.author?.username}
{message.message}
diff --git a/examples/slack-clone/nextjs-slack-clone/full-schema.sql b/examples/slack-clone/nextjs-slack-clone/full-schema.sql
index 58b277df895..1fafb7f5bef 100644
--- a/examples/slack-clone/nextjs-slack-clone/full-schema.sql
+++ b/examples/slack-clone/nextjs-slack-clone/full-schema.sql
@@ -9,7 +9,7 @@ create type public.user_status as enum ('ONLINE', 'OFFLINE');
-- USERS
create table public.users (
- id uuid not null primary key, -- UUID from auth.users
+ id uuid references auth.users not null primary key, -- UUID from auth.users
username text,
status user_status default 'OFFLINE'::public.user_status
);
@@ -64,14 +64,13 @@ declare
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
+ and role_permissions.role = (auth.jwt() ->> 'user_role')::public.app_role
into bind_permissions;
return bind_permissions > 0;
end;
-$$ language plpgsql security definer;
+$$ language plpgsql security definer set search_path = public;
-- Secure the tables
alter table public.users enable row level security;
@@ -116,7 +115,7 @@ begin
return new;
end;
-$$ language plpgsql security definer;
+$$ language plpgsql security definer set search_path = auth, public;
-- trigger the function every time a user is created
create trigger on_auth_user_created
@@ -141,24 +140,86 @@ 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');
+/**
+ * AUTH HOOKS
+ * Create an auth hook to add a custom claim to the access token jwt.
+ */
-insert into public.channels (slug, created_by)
-values
- ('public', '8d0fd2b3-9ca7-4d9e-a95f-9e13dded323e'),
- ('random', '8d0fd2b3-9ca7-4d9e-a95f-9e13dded323e');
+-- Create the auth hook function
+-- https://supabase.com/docs/guides/auth/auth-hooks#hook-custom-access-token
+create or replace function public.custom_access_token_hook(event jsonb)
+returns jsonb
+language plpgsql
+immutable
+as $$
+ declare
+ claims jsonb;
+ user_role public.app_role;
+ begin
+ -- Check if the user is marked as admin in the profiles table
+ select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid;
-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');
+ claims := event->'claims';
+
+ if user_role is not null then
+ -- Set the claim
+ claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
+ else
+ claims := jsonb_set(claims, '{user_role}', 'null');
+ end if;
+
+ -- Update the 'claims' object in the original event
+ event := jsonb_set(event, '{claims}', claims);
+
+ -- Return the modified or original event
+ return event;
+ end;
+$$;
+
+grant usage on schema public to supabase_auth_admin;
+
+grant execute
+ on function public.custom_access_token_hook
+ to supabase_auth_admin;
+
+revoke execute
+ on function public.custom_access_token_hook
+ from authenticated, anon;
+
+grant all
+ on table public.user_roles
+to supabase_auth_admin;
+
+revoke all
+ on table public.user_roles
+ from authenticated, anon;
+
+create policy "Allow auth admin to read user roles" ON public.user_roles
+as permissive for select
+to supabase_auth_admin
+using (true)
+
+
+/**
+ * HELPER FUNCTIONS
+ * Create test user helper method.
+ */
+create or replace function public.create_user(
+ email text
+) returns uuid
+ security definer
+ set search_path = auth
+as $$
+ declare
+ user_id uuid;
+begin
+ user_id := extensions.uuid_generate_v4();
-insert into public.role_permissions (role, permission)
-values
- ('admin', 'channels.delete'),
- ('admin', 'messages.delete'),
- ('moderator', 'messages.delete');
+ insert into auth.users (id, email)
+ values (user_id, email)
+ returning id into user_id;
+
+ return user_id;
+end;
+$$ language plpgsql;
diff --git a/examples/slack-clone/nextjs-slack-clone/lib/Store.js b/examples/slack-clone/nextjs-slack-clone/lib/Store.js
index c4382c872c4..545cf55e372 100644
--- a/examples/slack-clone/nextjs-slack-clone/lib/Store.js
+++ b/examples/slack-clone/nextjs-slack-clone/lib/Store.js
@@ -1,7 +1,10 @@
import { useState, useEffect } from 'react'
-import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
+import { createClient } from '@supabase/supabase-js'
-export const supabase = createPagesBrowserClient()
+export const supabase = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
+)
/**
* @param {number} channelId the currently selected Channel
@@ -23,38 +26,28 @@ export const useStore = (props) => {
// Listen for new and deleted messages
const messageListener = supabase
.channel('public:messages')
- .on(
- 'postgres_changes',
- { event: 'INSERT', schema: 'public', table: 'messages' },
- (payload) => handleNewMessage(payload.new)
+ .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) =>
+ handleNewMessage(payload.new)
)
- .on(
- 'postgres_changes',
- { event: 'DELETE', schema: 'public', table: 'messages' },
- (payload) => handleDeletedMessage(payload.old)
+ .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'messages' }, (payload) =>
+ handleDeletedMessage(payload.old)
)
.subscribe()
// Listen for changes to our users
const userListener = supabase
.channel('public:users')
- .on(
- 'postgres_changes',
- { event: '*', schema: 'public', table: 'users' },
- (payload) => handleNewOrUpdatedUser(payload.new)
+ .on('postgres_changes', { event: '*', schema: 'public', table: 'users' }, (payload) =>
+ handleNewOrUpdatedUser(payload.new)
)
.subscribe()
// Listen for new and deleted channels
const channelListener = supabase
.channel('public:channels')
- .on(
- 'postgres_changes',
- { event: 'INSERT', schema: 'public', table: 'channels' },
- (payload) => handleNewChannel(payload.new)
+ .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'channels' }, (payload) =>
+ handleNewChannel(payload.new)
)
- .on(
- 'postgres_changes',
- { event: 'DELETE', schema: 'public', table: 'channels' },
- (payload) => handleDeletedChannel(payload.old)
+ .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'channels' }, (payload) =>
+ handleDeletedChannel(payload.old)
)
.subscribe()
// Cleanup on unmount
@@ -151,20 +144,6 @@ export const fetchUser = async (userId, setState) => {
}
}
-/**
- * Fetch all roles for the current user
- * @param {function} setState Optionally pass in a hook or callback to set the state
- */
-export const fetchUserRoles = async (setState) => {
- try {
- let { data } = await supabase.from('user_roles').select(`*`)
- if (setState) setState(data)
- return data
- } catch (error) {
- console.log('error', error)
- }
-}
-
/**
* Fetch all messages and their authors
* @param {number} channelId
@@ -191,7 +170,10 @@ export const fetchMessages = async (channelId, setState) => {
*/
export const addChannel = async (slug, user_id) => {
try {
- let { data } = await supabase.from('channels').insert([{ slug, created_by: user_id }]).select()
+ let { data } = await supabase
+ .from('channels')
+ .insert([{ slug, created_by: user_id }])
+ .select()
return data
} catch (error) {
console.log('error', error)
@@ -206,7 +188,10 @@ export const addChannel = async (slug, user_id) => {
*/
export const addMessage = async (message, channel_id, user_id) => {
try {
- let { data } = await supabase.from('messages').insert([{ message, channel_id, user_id }]).select()
+ let { data } = await supabase
+ .from('messages')
+ .insert([{ message, channel_id, user_id }])
+ .select()
return data
} catch (error) {
console.log('error', error)
diff --git a/examples/slack-clone/nextjs-slack-clone/package-lock.json b/examples/slack-clone/nextjs-slack-clone/package-lock.json
index dd187f4f898..2ac89187de9 100644
--- a/examples/slack-clone/nextjs-slack-clone/package-lock.json
+++ b/examples/slack-clone/nextjs-slack-clone/package-lock.json
@@ -9,8 +9,8 @@
"version": "0.2.0",
"license": "MIT",
"dependencies": {
- "@supabase/auth-helpers-nextjs": "^0.7.2",
- "@supabase/supabase-js": "^2.26.0",
+ "@supabase/supabase-js": "^2.39.6",
+ "jwt-decode": "^4.0.0",
"next": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -307,98 +307,90 @@
"node": ">= 8"
}
},
- "node_modules/@supabase/auth-helpers-nextjs": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.7.2.tgz",
- "integrity": "sha512-n5IyGBYJV/WiR5Rgw4CUaiJYiOv9yW2of4ZP4EyzKt2O6/6rztt7PcGE4AoK2vERw+fb5F2QtJBdt6J5eOYCCw==",
- "dependencies": {
- "@supabase/auth-helpers-shared": "0.4.1",
- "set-cookie-parser": "^2.6.0"
- },
- "peerDependencies": {
- "@supabase/supabase-js": "^2.19.0"
- }
- },
- "node_modules/@supabase/auth-helpers-shared": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.4.1.tgz",
- "integrity": "sha512-IEDX9JzWkIjQiLUaP4Qy5YDiG0jFQatWfS+jw8cCQs6QfbNdEPd2Y3qonwGHnM90CZom9SvjuylBv2pFVAL7Lw==",
- "dependencies": {
- "jose": "^4.14.3"
- },
- "peerDependencies": {
- "@supabase/supabase-js": "^2.19.0"
- }
- },
"node_modules/@supabase/functions-js": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.2.tgz",
- "integrity": "sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==",
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz",
+ "integrity": "sha512-BNzC5XhCzzCaggJ8s53DP+WeHHGT/NfTsx2wUSSGKR2/ikLFQTBCDzMvGz/PxYMqRko/LwncQtKXGOYp1PkPaw==",
"dependencies": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/gotrue-js": {
- "version": "2.38.0",
- "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.38.0.tgz",
- "integrity": "sha512-iitNthPLQjAD73FtnUNUalwPFpsiiE7t/0mVw14b+3rqzr52Efri8gAjzT9hi7p35uOLA76HMY3aWJj6Gy7ozw==",
+ "version": "2.62.2",
+ "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.62.2.tgz",
+ "integrity": "sha512-AP6e6W9rQXFTEJ7sTTNYQrNf0LCcnt1hUW+RIgUK+Uh3jbWvcIST7wAlYyNZiMlS9+PYyymWQ+Ykz/rOYSO0+A==",
"dependencies": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/node-fetch": {
+ "version": "2.6.15",
+ "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
+ "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
}
},
"node_modules/@supabase/postgrest-js": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.7.1.tgz",
- "integrity": "sha512-xPRYLaZrkLbXNlzmHW6Wtf9hmcBLjjI5xUz2zj8oE2hgXGaYoZBBkpN9bmW9i17Z1f6Ujxa942AqK439XOA36A==",
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.9.2.tgz",
+ "integrity": "sha512-I6yHo8CC9cxhOo6DouDMy9uOfW7hjdsnCxZiaJuIVZm1dBGTFiQPgfMa9zXCamEWzNyWRjZvupAUuX+tqcl5Sw==",
"dependencies": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/realtime-js": {
- "version": "2.7.3",
- "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.3.tgz",
- "integrity": "sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==",
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.3.tgz",
+ "integrity": "sha512-lAp50s2n3FhGJFq+wTSXLNIDPw5Y0Wxrgt44eM5nLSA3jZNUUP3Oq2Ccd1CbZdVntPCWLZvJaU//pAd2NE+QnQ==",
"dependencies": {
+ "@supabase/node-fetch": "^2.6.14",
"@types/phoenix": "^1.5.4",
- "@types/websocket": "^1.0.3",
- "websocket": "^1.0.34"
+ "@types/ws": "^8.5.10",
+ "ws": "^8.14.2"
}
},
"node_modules/@supabase/storage-js": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.1.tgz",
- "integrity": "sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==",
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz",
+ "integrity": "sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==",
"dependencies": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/supabase-js": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.26.0.tgz",
- "integrity": "sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==",
+ "version": "2.39.6",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.39.6.tgz",
+ "integrity": "sha512-HlflDzem0+l3KYYTqHV0UsqkDooV9my5UcBCV2zvvTrl77UtW97uKTZWn9lSWMuiy+ZvRLsiuG+WTiBuKMQl0Q==",
"dependencies": {
- "@supabase/functions-js": "^2.1.0",
- "@supabase/gotrue-js": "^2.31.0",
- "@supabase/postgrest-js": "^1.7.0",
- "@supabase/realtime-js": "^2.7.3",
- "@supabase/storage-js": "^2.5.1",
- "cross-fetch": "^3.1.5"
+ "@supabase/functions-js": "2.1.5",
+ "@supabase/gotrue-js": "2.62.2",
+ "@supabase/node-fetch": "2.6.15",
+ "@supabase/postgrest-js": "1.9.2",
+ "@supabase/realtime-js": "2.9.3",
+ "@supabase/storage-js": "2.5.5"
}
},
"node_modules/@types/node": {
- "version": "20.3.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz",
- "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw=="
+ "version": "20.11.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
+ "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
},
"node_modules/@types/phoenix": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.0.tgz",
- "integrity": "sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g=="
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.4.tgz",
+ "integrity": "sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA=="
},
- "node_modules/@types/websocket": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz",
- "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==",
+ "node_modules/@types/ws": {
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
+ "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"dependencies": {
"@types/node": "*"
}
@@ -530,18 +522,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/bufferutil": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
- "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
- "hasInstallScript": true,
- "dependencies": {
- "node-gyp-build": "^4.3.0"
- },
- "engines": {
- "node": ">=6.14.2"
- }
- },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -612,14 +592,6 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
- "node_modules/cross-fetch": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.7.tgz",
- "integrity": "sha512-Ff9FKeIMm0Rx1o8TEV87bTK5M232akt7uSAYrSTU/QA/W6Jj9P+fWn1mxGgl+dwDzpFoAY35OIS2SJXA8WEWKA==",
- "dependencies": {
- "node-fetch": "2.6.12"
- }
- },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -632,23 +604,6 @@
"node": ">=4"
}
},
- "node_modules/d": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
- "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
- "dependencies": {
- "es5-ext": "^0.10.50",
- "type": "^1.0.1"
- }
- },
- "node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -667,39 +622,6 @@
"integrity": "sha512-sxX0LXh+uL41hSJsujAN86PjhrV/6c79XmpY0TvjZStV6VxIgarf8SRkUoUTuYmFcZQTemsoqo8qXOGw5npWfw==",
"dev": true
},
- "node_modules/es5-ext": {
- "version": "0.10.62",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
- "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
- "hasInstallScript": true,
- "dependencies": {
- "es6-iterator": "^2.0.3",
- "es6-symbol": "^3.1.3",
- "next-tick": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/es6-iterator": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
- "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
- "dependencies": {
- "d": "1",
- "es5-ext": "^0.10.35",
- "es6-symbol": "^3.1.1"
- }
- },
- "node_modules/es6-symbol": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
- "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
- "dependencies": {
- "d": "^1.0.1",
- "ext": "^1.1.2"
- }
- },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -709,19 +631,6 @@
"node": ">=6"
}
},
- "node_modules/ext": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
- "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
- "dependencies": {
- "type": "^2.7.2"
- }
- },
- "node_modules/ext/node_modules/type": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
- "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
- },
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@@ -919,11 +828,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
- },
"node_modules/jiti": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
@@ -933,19 +837,19 @@
"jiti": "bin/jiti.js"
}
},
- "node_modules/jose": {
- "version": "4.14.4",
- "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
- "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==",
- "funding": {
- "url": "https://github.com/sponsors/panva"
- }
- },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -1006,11 +910,6 @@
"node": "*"
}
},
- "node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
- },
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -1088,11 +987,6 @@
}
}
},
- "node_modules/next-tick": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
- "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
- },
"node_modules/next/node_modules/postcss": {
"version": "8.4.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
@@ -1110,35 +1004,6 @@
"url": "https://opencollective.com/postcss/"
}
},
- "node_modules/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/node-gyp-build": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
- "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==",
- "bin": {
- "node-gyp-build": "bin.js",
- "node-gyp-build-optional": "optional.js",
- "node-gyp-build-test": "build-test.js"
- }
- },
"node_modules/node-releases": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
@@ -1492,11 +1357,6 @@
"loose-envify": "^1.1.0"
}
},
- "node_modules/set-cookie-parser": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
- "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
- },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -1671,18 +1531,10 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
},
- "node_modules/type": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
- "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
- },
- "node_modules/typedarray-to-buffer": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
- "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
- "dependencies": {
- "is-typedarray": "^1.0.0"
- }
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/update-browserslist-db": {
"version": "1.0.11",
@@ -1714,18 +1566,6 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/utf-8-validate": {
- "version": "5.0.10",
- "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
- "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
- "hasInstallScript": true,
- "dependencies": {
- "node-gyp-build": "^4.3.0"
- },
- "engines": {
- "node": ">=6.14.2"
- }
- },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -1737,22 +1577,6 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
- "node_modules/websocket": {
- "version": "1.0.34",
- "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
- "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
- "dependencies": {
- "bufferutil": "^4.0.1",
- "debug": "^2.2.0",
- "es5-ext": "^0.10.50",
- "typedarray-to-buffer": "^3.1.5",
- "utf-8-validate": "^5.0.2",
- "yaeti": "^0.0.6"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -1768,12 +1592,24 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
- "node_modules/yaeti": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
- "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
+ "node_modules/ws": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+ "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"engines": {
- "node": ">=0.10.32"
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
}
},
"node_modules/yaml": {
@@ -1943,92 +1779,87 @@
"fastq": "^1.6.0"
}
},
- "@supabase/auth-helpers-nextjs": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.7.2.tgz",
- "integrity": "sha512-n5IyGBYJV/WiR5Rgw4CUaiJYiOv9yW2of4ZP4EyzKt2O6/6rztt7PcGE4AoK2vERw+fb5F2QtJBdt6J5eOYCCw==",
- "requires": {
- "@supabase/auth-helpers-shared": "0.4.1",
- "set-cookie-parser": "^2.6.0"
- }
- },
- "@supabase/auth-helpers-shared": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.4.1.tgz",
- "integrity": "sha512-IEDX9JzWkIjQiLUaP4Qy5YDiG0jFQatWfS+jw8cCQs6QfbNdEPd2Y3qonwGHnM90CZom9SvjuylBv2pFVAL7Lw==",
- "requires": {
- "jose": "^4.14.3"
- }
- },
"@supabase/functions-js": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.2.tgz",
- "integrity": "sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==",
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.5.tgz",
+ "integrity": "sha512-BNzC5XhCzzCaggJ8s53DP+WeHHGT/NfTsx2wUSSGKR2/ikLFQTBCDzMvGz/PxYMqRko/LwncQtKXGOYp1PkPaw==",
"requires": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
}
},
"@supabase/gotrue-js": {
- "version": "2.38.0",
- "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.38.0.tgz",
- "integrity": "sha512-iitNthPLQjAD73FtnUNUalwPFpsiiE7t/0mVw14b+3rqzr52Efri8gAjzT9hi7p35uOLA76HMY3aWJj6Gy7ozw==",
+ "version": "2.62.2",
+ "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.62.2.tgz",
+ "integrity": "sha512-AP6e6W9rQXFTEJ7sTTNYQrNf0LCcnt1hUW+RIgUK+Uh3jbWvcIST7wAlYyNZiMlS9+PYyymWQ+Ykz/rOYSO0+A==",
"requires": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "@supabase/node-fetch": {
+ "version": "2.6.15",
+ "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
+ "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
+ "requires": {
+ "whatwg-url": "^5.0.0"
}
},
"@supabase/postgrest-js": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.7.1.tgz",
- "integrity": "sha512-xPRYLaZrkLbXNlzmHW6Wtf9hmcBLjjI5xUz2zj8oE2hgXGaYoZBBkpN9bmW9i17Z1f6Ujxa942AqK439XOA36A==",
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.9.2.tgz",
+ "integrity": "sha512-I6yHo8CC9cxhOo6DouDMy9uOfW7hjdsnCxZiaJuIVZm1dBGTFiQPgfMa9zXCamEWzNyWRjZvupAUuX+tqcl5Sw==",
"requires": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
}
},
"@supabase/realtime-js": {
- "version": "2.7.3",
- "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.3.tgz",
- "integrity": "sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==",
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.9.3.tgz",
+ "integrity": "sha512-lAp50s2n3FhGJFq+wTSXLNIDPw5Y0Wxrgt44eM5nLSA3jZNUUP3Oq2Ccd1CbZdVntPCWLZvJaU//pAd2NE+QnQ==",
"requires": {
+ "@supabase/node-fetch": "^2.6.14",
"@types/phoenix": "^1.5.4",
- "@types/websocket": "^1.0.3",
- "websocket": "^1.0.34"
+ "@types/ws": "^8.5.10",
+ "ws": "^8.14.2"
}
},
"@supabase/storage-js": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.1.tgz",
- "integrity": "sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==",
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz",
+ "integrity": "sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==",
"requires": {
- "cross-fetch": "^3.1.5"
+ "@supabase/node-fetch": "^2.6.14"
}
},
"@supabase/supabase-js": {
- "version": "2.26.0",
- "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.26.0.tgz",
- "integrity": "sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==",
+ "version": "2.39.6",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.39.6.tgz",
+ "integrity": "sha512-HlflDzem0+l3KYYTqHV0UsqkDooV9my5UcBCV2zvvTrl77UtW97uKTZWn9lSWMuiy+ZvRLsiuG+WTiBuKMQl0Q==",
"requires": {
- "@supabase/functions-js": "^2.1.0",
- "@supabase/gotrue-js": "^2.31.0",
- "@supabase/postgrest-js": "^1.7.0",
- "@supabase/realtime-js": "^2.7.3",
- "@supabase/storage-js": "^2.5.1",
- "cross-fetch": "^3.1.5"
+ "@supabase/functions-js": "2.1.5",
+ "@supabase/gotrue-js": "2.62.2",
+ "@supabase/node-fetch": "2.6.15",
+ "@supabase/postgrest-js": "1.9.2",
+ "@supabase/realtime-js": "2.9.3",
+ "@supabase/storage-js": "2.5.5"
}
},
"@types/node": {
- "version": "20.3.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz",
- "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw=="
+ "version": "20.11.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
+ "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
+ "requires": {
+ "undici-types": "~5.26.4"
+ }
},
"@types/phoenix": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.0.tgz",
- "integrity": "sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g=="
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.4.tgz",
+ "integrity": "sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA=="
},
- "@types/websocket": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz",
- "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==",
+ "@types/ws": {
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
+ "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
"requires": {
"@types/node": "*"
}
@@ -2112,14 +1943,6 @@
"update-browserslist-db": "^1.0.11"
}
},
- "bufferutil": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
- "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
- "requires": {
- "node-gyp-build": "^4.3.0"
- }
- },
"camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -2159,37 +1982,12 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
- "cross-fetch": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.7.tgz",
- "integrity": "sha512-Ff9FKeIMm0Rx1o8TEV87bTK5M232akt7uSAYrSTU/QA/W6Jj9P+fWn1mxGgl+dwDzpFoAY35OIS2SJXA8WEWKA==",
- "requires": {
- "node-fetch": "2.6.12"
- }
- },
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
- "d": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
- "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
- "requires": {
- "es5-ext": "^0.10.50",
- "type": "^1.0.1"
- }
- },
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
"didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -2208,56 +2006,12 @@
"integrity": "sha512-sxX0LXh+uL41hSJsujAN86PjhrV/6c79XmpY0TvjZStV6VxIgarf8SRkUoUTuYmFcZQTemsoqo8qXOGw5npWfw==",
"dev": true
},
- "es5-ext": {
- "version": "0.10.62",
- "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
- "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
- "requires": {
- "es6-iterator": "^2.0.3",
- "es6-symbol": "^3.1.3",
- "next-tick": "^1.1.0"
- }
- },
- "es6-iterator": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
- "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
- "requires": {
- "d": "1",
- "es5-ext": "^0.10.35",
- "es6-symbol": "^3.1.1"
- }
- },
- "es6-symbol": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
- "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
- "requires": {
- "d": "^1.0.1",
- "ext": "^1.1.2"
- }
- },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true
},
- "ext": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
- "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
- "requires": {
- "type": "^2.7.2"
- },
- "dependencies": {
- "type": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
- "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
- }
- }
- },
"fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@@ -2408,27 +2162,22 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"devOptional": true
},
- "is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
- },
"jiti": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
"dev": true
},
- "jose": {
- "version": "4.14.4",
- "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
- "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g=="
- },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
+ "jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="
+ },
"lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -2474,11 +2223,6 @@
"brace-expansion": "^1.1.7"
}
},
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
- },
"mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -2530,24 +2274,6 @@
}
}
},
- "next-tick": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
- "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
- },
- "node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
- "requires": {
- "whatwg-url": "^5.0.0"
- }
- },
- "node-gyp-build": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
- "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ=="
- },
"node-releases": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
@@ -2766,11 +2492,6 @@
"loose-envify": "^1.1.0"
}
},
- "set-cookie-parser": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
- "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
- },
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -2892,18 +2613,10 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
},
- "type": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
- "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
- },
- "typedarray-to-buffer": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
- "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
- "requires": {
- "is-typedarray": "^1.0.0"
- }
+ "undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"update-browserslist-db": {
"version": "1.0.11",
@@ -2915,14 +2628,6 @@
"picocolors": "^1.0.0"
}
},
- "utf-8-validate": {
- "version": "5.0.10",
- "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
- "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
- "requires": {
- "node-gyp-build": "^4.3.0"
- }
- },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -2934,19 +2639,6 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
- "websocket": {
- "version": "1.0.34",
- "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
- "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
- "requires": {
- "bufferutil": "^4.0.1",
- "debug": "^2.2.0",
- "es5-ext": "^0.10.50",
- "typedarray-to-buffer": "^3.1.5",
- "utf-8-validate": "^5.0.2",
- "yaeti": "^0.0.6"
- }
- },
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -2962,10 +2654,11 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
- "yaeti": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
- "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug=="
+ "ws": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+ "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+ "requires": {}
},
"yaml": {
"version": "2.3.1",
diff --git a/examples/slack-clone/nextjs-slack-clone/package.json b/examples/slack-clone/nextjs-slack-clone/package.json
index 4af29dafd8a..ecdf31d00da 100644
--- a/examples/slack-clone/nextjs-slack-clone/package.json
+++ b/examples/slack-clone/nextjs-slack-clone/package.json
@@ -9,8 +9,8 @@
"start": "next start"
},
"dependencies": {
- "@supabase/supabase-js": "^2.26.0",
- "@supabase/auth-helpers-nextjs": "^0.7.2",
+ "@supabase/supabase-js": "^2.39.6",
+ "jwt-decode": "^4.0.0",
"next": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
diff --git a/examples/slack-clone/nextjs-slack-clone/pages/_app.js b/examples/slack-clone/nextjs-slack-clone/pages/_app.js
index 3db2d706fa3..d915c72915d 100644
--- a/examples/slack-clone/nextjs-slack-clone/pages/_app.js
+++ b/examples/slack-clone/nextjs-slack-clone/pages/_app.js
@@ -2,13 +2,13 @@ import '~/styles/style.scss'
import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import UserContext from 'lib/UserContext'
-import { supabase, fetchUserRoles } from 'lib/Store'
+import { supabase } from 'lib/Store'
+import { jwtDecode } from 'jwt-decode'
export default function SupabaseSlackClone({ Component, pageProps }) {
const [userLoaded, setUserLoaded] = useState(false)
const [user, setUser] = useState(null)
const [session, setSession] = useState(null)
- const [userRoles, setUserRoles] = useState([])
const router = useRouter()
useEffect(() => {
@@ -18,27 +18,31 @@ export default function SupabaseSlackClone({ Component, pageProps }) {
) {
setSession(session)
const currentUser = session?.user
+ if (session) {
+ const jwt = jwtDecode(session.access_token)
+ currentUser.appRole = jwt.user_role
+ }
setUser(currentUser ?? null)
setUserLoaded(!!currentUser)
if (currentUser) {
- signIn(currentUser.id, currentUser.email)
router.push('/channels/[id]', '/channels/1')
}
}
- supabase.auth.getSession().then(({ data: { session }}) => saveSession(session))
+ supabase.auth.getSession().then(({ data: { session } }) => saveSession(session))
- const { subscription: authListener } = supabase.auth.onAuthStateChange(async (event, session) => saveSession(session))
+ const { subscription: authListener } = supabase.auth.onAuthStateChange(
+ async (event, session) => {
+ console.log(session)
+ saveSession(session)
+ }
+ )
return () => {
authListener.unsubscribe()
}
}, [])
- const signIn = async () => {
- await fetchUserRoles((userRoles) => setUserRoles(userRoles.map((userRole) => userRole.role)))
- }
-
const signOut = async () => {
const { error } = await supabase.auth.signOut()
if (!error) {
@@ -51,8 +55,6 @@ export default function SupabaseSlackClone({ Component, pageProps }) {
value={{
userLoaded,
user,
- userRoles,
- signIn,
signOut,
}}
>
diff --git a/examples/slack-clone/nextjs-slack-clone/supabase/.gitignore b/examples/slack-clone/nextjs-slack-clone/supabase/.gitignore
new file mode 100644
index 00000000000..a3ad88055b7
--- /dev/null
+++ b/examples/slack-clone/nextjs-slack-clone/supabase/.gitignore
@@ -0,0 +1,4 @@
+# Supabase
+.branches
+.temp
+.env
diff --git a/examples/slack-clone/nextjs-slack-clone/supabase/config.toml b/examples/slack-clone/nextjs-slack-clone/supabase/config.toml
new file mode 100644
index 00000000000..c5ff45b294f
--- /dev/null
+++ b/examples/slack-clone/nextjs-slack-clone/supabase/config.toml
@@ -0,0 +1,155 @@
+# A string used to distinguish different Supabase projects on the same host. Defaults to the
+# working directory name when running `supabase init`.
+project_id = "slack-clone"
+
+[api]
+enabled = true
+# Port to use for the API URL.
+port = 54321
+# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
+# endpoints. public and storage are always included.
+schemas = ["public", "storage", "graphql_public"]
+# Extra schemas to add to the search_path of every request. public is always included.
+extra_search_path = ["public", "extensions"]
+# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
+# for accidental or malicious requests.
+max_rows = 1000
+
+[db]
+# Port to use for the local database URL.
+port = 54322
+# Port used by db diff command to initialize the shadow database.
+shadow_port = 54320
+# The database major version to use. This has to be the same as your remote database's. Run `SHOW
+# server_version;` on the remote database to check.
+major_version = 15
+
+[db.pooler]
+enabled = false
+# Port to use for the local connection pooler.
+port = 54329
+# Specifies when a server connection can be reused by other clients.
+# Configure one of the supported pooler modes: `transaction`, `session`.
+pool_mode = "transaction"
+# How many server connections to allow per user/database pair.
+default_pool_size = 20
+# Maximum number of client connections allowed.
+max_client_conn = 100
+
+[realtime]
+enabled = true
+# Bind realtime via either IPv4 or IPv6. (default: IPv6)
+# ip_version = "IPv6"
+
+[studio]
+enabled = true
+# Port to use for Supabase Studio.
+port = 54323
+# External URL of the API server that frontend connects to.
+api_url = "http://127.0.0.1"
+
+# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
+# are monitored, and you can view the emails that would have been sent from the web interface.
+[inbucket]
+enabled = true
+# Port to use for the email testing server web interface.
+port = 54324
+# Uncomment to expose additional ports for testing user applications that send emails.
+# smtp_port = 54325
+# pop3_port = 54326
+
+[storage]
+enabled = true
+# The maximum file size allowed (e.g. "5MB", "500KB").
+file_size_limit = "50MiB"
+
+[auth]
+enabled = true
+# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
+# in emails.
+site_url = "http://127.0.0.1:3000"
+# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
+additional_redirect_urls = ["https://127.0.0.1:3000"]
+# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
+jwt_expiry = 3600
+# If disabled, the refresh token will never expire.
+enable_refresh_token_rotation = true
+# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
+# Requires enable_refresh_token_rotation = true.
+refresh_token_reuse_interval = 10
+# Allow/disallow new user signups to your project.
+enable_signup = true
+
+[auth.email]
+# Allow/disallow new user signups via email to your project.
+enable_signup = true
+# If enabled, a user will be required to confirm any email change on both the old, and new email
+# addresses. If disabled, only the new email is required to confirm.
+double_confirm_changes = true
+# If enabled, users need to confirm their email address before signing in.
+enable_confirmations = false
+
+# Uncomment to customize email template
+# [auth.email.template.invite]
+# subject = "You have been invited"
+# content_path = "./supabase/templates/invite.html"
+
+[auth.sms]
+# Allow/disallow new user signups via SMS to your project.
+enable_signup = true
+# If enabled, users need to confirm their phone number before signing in.
+enable_confirmations = false
+# Template for sending OTP to users
+template = "Your code is {{ .Code }} ."
+
+# Use pre-defined map of phone number to OTP for testing.
+[auth.sms.test_otp]
+# 4152127777 = "123456"
+
+# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
+[auth.sms.twilio]
+enabled = false
+account_sid = ""
+message_service_sid = ""
+# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
+auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
+
+# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
+# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`,
+# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
+[auth.external.apple]
+enabled = false
+client_id = ""
+# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
+secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
+# Overrides the default auth redirectUrl.
+redirect_uri = ""
+# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
+# or any other third-party OIDC providers.
+url = ""
+
+# Enable auth hooks
+# https://supabase.com/docs/guides/auth/auth-hooks#local-development
+[auth.hook.custom_access_token]
+enabled = true
+uri = "pg-functions://postgres/public/custom_access_token_hook"
+
+[analytics]
+enabled = false
+port = 54327
+vector_port = 54328
+# Configure one of the supported backends: `postgres`, `bigquery`.
+backend = "postgres"
+
+# Experimental features may be deprecated any time
+[experimental]
+# Configures Postgres storage engine to use OrioleDB (S3)
+orioledb_version = ""
+# Configures S3 bucket URL, eg. .s3-.amazonaws.com
+s3_host = "env(S3_HOST)"
+# Configures S3 bucket region, eg. us-east-1
+s3_region = "env(S3_REGION)"
+# Configures AWS_ACCESS_KEY_ID for S3 bucket
+s3_access_key = "env(S3_ACCESS_KEY)"
+# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
+s3_secret_key = "env(S3_SECRET_KEY)"
diff --git a/examples/slack-clone/nextjs-slack-clone/supabase/migrations/20240214102356_init.sql b/examples/slack-clone/nextjs-slack-clone/supabase/migrations/20240214102356_init.sql
new file mode 100644
index 00000000000..fd568a6d106
--- /dev/null
+++ b/examples/slack-clone/nextjs-slack-clone/supabase/migrations/20240214102356_init.sql
@@ -0,0 +1,165 @@
+--
+-- For use with https://github.com/supabase/supabase/tree/master/examples/slack-clone/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 references auth.users 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 (
+ id bigint generated by default as identity primary key,
+ 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 (
+ id bigint generated by default as identity primary key,
+ 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(
+ requested_permission app_permission,
+ user_id uuid
+)
+returns boolean as $$
+declare
+ bind_permissions int;
+begin
+ select count(*)
+ from public.role_permissions
+ where role_permissions.permission = authorize.requested_permission
+ and role_permissions.role = (auth.jwt() ->> 'user_role')::public.app_role
+ into bind_permissions;
+
+ return bind_permissions > 0;
+end;
+$$ language plpgsql security definer set search_path = public;
+
+-- 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 );
+
+-- 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;
+
+-- 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 set search_path = auth, public;
+
+-- 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;
+
+/**
+ * HELPER FUNCTIONS
+ * Create test user helper method.
+ */
+create or replace function public.create_user(
+ email text
+) returns uuid
+ security definer
+ set search_path = auth
+as $$
+ declare
+ user_id uuid;
+begin
+ user_id := extensions.uuid_generate_v4();
+
+ insert into auth.users (id, email)
+ values (user_id, email)
+ returning id into user_id;
+
+ return user_id;
+end;
+$$ language plpgsql;
+
diff --git a/examples/slack-clone/nextjs-slack-clone/supabase/migrations/20240214114147_auth-hook.sql b/examples/slack-clone/nextjs-slack-clone/supabase/migrations/20240214114147_auth-hook.sql
new file mode 100644
index 00000000000..ea17d1dec7a
--- /dev/null
+++ b/examples/slack-clone/nextjs-slack-clone/supabase/migrations/20240214114147_auth-hook.sql
@@ -0,0 +1,58 @@
+/**
+ * AUTH HOOKS
+ * Create an auth hook to add a custom claim to the access token jwt.
+ */
+
+-- Create the auth hook function
+-- https://supabase.com/docs/guides/auth/auth-hooks#hook-custom-access-token
+create or replace function public.custom_access_token_hook(event jsonb)
+returns jsonb
+language plpgsql
+immutable
+as $$
+ declare
+ claims jsonb;
+ user_role public.app_role;
+ begin
+ -- Check if the user is marked as admin in the profiles table
+ select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid;
+
+ claims := event->'claims';
+
+ if user_role is not null then
+ -- Set the claim
+ claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
+ else
+ claims := jsonb_set(claims, '{user_role}', 'null');
+ end if;
+
+ -- Update the 'claims' object in the original event
+ event := jsonb_set(event, '{claims}', claims);
+
+ -- Return the modified or original event
+ return event;
+ end;
+$$;
+
+grant usage on schema public to supabase_auth_admin;
+
+grant execute
+ on function public.custom_access_token_hook
+ to supabase_auth_admin;
+
+revoke execute
+ on function public.custom_access_token_hook
+ from authenticated, anon;
+
+grant all
+ on table public.user_roles
+to supabase_auth_admin;
+
+revoke all
+ on table public.user_roles
+ from authenticated, anon;
+
+create policy "Allow auth admin to read user roles" ON public.user_roles
+as permissive for select
+to supabase_auth_admin
+using (true)
diff --git a/examples/slack-clone/nextjs-slack-clone/supabase/seed.sql b/examples/slack-clone/nextjs-slack-clone/supabase/seed.sql
new file mode 100644
index 00000000000..5e208015da7
--- /dev/null
+++ b/examples/slack-clone/nextjs-slack-clone/supabase/seed.sql
@@ -0,0 +1,23 @@
+insert into public.role_permissions (role, permission)
+values
+ ('admin', 'channels.delete'),
+ ('admin', 'messages.delete'),
+ ('moderator', 'messages.delete');
+
+DO $$
+DECLARE
+ user_id uuid;
+BEGIN
+ user_id := public.create_user('supabot+supaadmin@example.com');
+
+ insert into public.channels (slug, created_by)
+ values
+ ('public', user_id),
+ ('random', user_id);
+
+ insert into public.messages (message, channel_id, user_id)
+ values
+ ('Hello World 👋', 1, user_id),
+ ('Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.', 2, user_id);
+END $$;
+