mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat: add rbac claim auth hook. (#21263)
* feat: add rbac claim auth hook. * feat: get user role from jwt. * chore: update full-schema. * feat: add RBAC guide. * feat: use plpgsql. * Apply suggestions from code review Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> * chore: make top level claim. * chore: set search_paths. * chore: accessing custom claims. --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com>
This commit is contained in:
1 parent
f9a55935f5
commit
7ce6958030
16 files changed
+969
-708
No files matched your search
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.';
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
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).
|
||||
|
||||
</Admonition>
|
||||
|
||||
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.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="plpgsql"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="plpgsql" label="PL/pgSQL (best performance)">
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="plv8" label="JavaScript (PLV8)">
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### 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.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
To learn more about Auth Hooks, see the [Auth Hooks docs](/guides/auth/auth-hooks).
|
||||
|
||||
</Admonition>
|
||||
|
||||
## 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;
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
You can read more about using functions in RLS policies in the [RLS guide](/guides/auth/row-level-security#using-functions).
|
||||
|
||||
</Admonition>
|
||||
|
||||
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)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
@@ -73,13 +72,13 @@ export default function Layout(props) {
|
||||
)
|
||||
}
|
||||
|
||||
const SidebarItem = ({ channel, isActiveChannel, user, userRoles }) => (
|
||||
const SidebarItem = ({ channel, isActiveChannel, user }) => (
|
||||
<>
|
||||
<li className="flex items-center justify-between">
|
||||
<Link href="/channels/[id]" as={`/channels/${channel.id}`}>
|
||||
<a className={isActiveChannel ? 'font-bold' : ''}>{channel.slug}</a>
|
||||
</Link>
|
||||
{channel.id !== 1 && (channel.created_by === user?.id || userRoles.includes('admin')) && (
|
||||
{channel.id !== 1 && (channel.created_by === user?.id || user?.appRole === 'admin') && (
|
||||
<button onClick={() => deleteChannel(channel.id)}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
|
||||
@@ -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 (
|
||||
<div className="py-1 flex items-center space-x-2">
|
||||
<div className="text-gray-100 w-4">
|
||||
{(user?.id === message.user_id ||
|
||||
userRoles.some((role) => ['admin', 'moderator'].includes(role))) && (
|
||||
{(user?.id === message.user_id || ['admin', 'moderator'].includes(user?.appRole)) && (
|
||||
<button onClick={() => deleteMessage(message.id)}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-blue-700 font-bold">{message.author.username}</p>
|
||||
<p className="text-blue-700 font-bold">{message?.author?.username}</p>
|
||||
<p className="text-white">{message.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
+152
-459
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
.env
|
||||
@@ -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. <bucket_name>.s3-<region>.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)"
|
||||
@@ -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;
|
||||
|
||||
+58
@@ -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)
|
||||
@@ -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 $$;
|
||||
|
||||
Reference in new issue
Block a user