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') && ( 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)) && ( )}
    -

    {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 $$; +