feat: enhance service role handling and storage policies in migrations and schemas
This commit is contained in:
@@ -96,6 +96,9 @@ services:
|
|||||||
SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:?Set SUPABASE_SERVICE_ROLE_KEY in Dokploy env}
|
SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:?Set SUPABASE_SERVICE_ROLE_KEY in Dokploy env}
|
||||||
POSTGREST_URL: http://neta-rest:3000
|
POSTGREST_URL: http://neta-rest:3000
|
||||||
PGRST_JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in Dokploy env}
|
PGRST_JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in Dokploy env}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in Dokploy env}
|
||||||
|
AUTH_JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in Dokploy env}
|
||||||
|
SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:?Set SUPABASE_SERVICE_ROLE_KEY in Dokploy env}
|
||||||
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in Dokploy env}@neta-db:5432/postgres
|
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in Dokploy env}@neta-db:5432/postgres
|
||||||
FILE_SIZE_LIMIT: ${STORAGE_FILE_SIZE_LIMIT:-52428800}
|
FILE_SIZE_LIMIT: ${STORAGE_FILE_SIZE_LIMIT:-52428800}
|
||||||
STORAGE_BACKEND: file
|
STORAGE_BACKEND: file
|
||||||
@@ -115,7 +118,7 @@ services:
|
|||||||
neta-storage:
|
neta-storage:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
environment:
|
environment:
|
||||||
NETA_MIGRATION_RUNNER_VERSION: "2026-06-14.2"
|
NETA_MIGRATION_RUNNER_VERSION: "2026-06-14.3"
|
||||||
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in Dokploy env}@neta-db:5432/postgres
|
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in Dokploy env}@neta-db:5432/postgres
|
||||||
NETA_POSTGREST_RELOAD_WAIT_SECONDS: ${NETA_POSTGREST_RELOAD_WAIT_SECONDS:-2}
|
NETA_POSTGREST_RELOAD_WAIT_SECONDS: ${NETA_POSTGREST_RELOAD_WAIT_SECONDS:-2}
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ services:
|
|||||||
SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:?Set SUPABASE_SERVICE_ROLE_KEY in .env}
|
SERVICE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:?Set SUPABASE_SERVICE_ROLE_KEY in .env}
|
||||||
POSTGREST_URL: http://neta-rest:3000
|
POSTGREST_URL: http://neta-rest:3000
|
||||||
PGRST_JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
|
PGRST_JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
|
||||||
|
AUTH_JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
|
||||||
|
SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY:?Set SUPABASE_SERVICE_ROLE_KEY in .env}
|
||||||
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@neta-db:5432/postgres
|
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@neta-db:5432/postgres
|
||||||
FILE_SIZE_LIMIT: ${STORAGE_FILE_SIZE_LIMIT:-52428800}
|
FILE_SIZE_LIMIT: ${STORAGE_FILE_SIZE_LIMIT:-52428800}
|
||||||
STORAGE_BACKEND: file
|
STORAGE_BACKEND: file
|
||||||
@@ -121,7 +124,7 @@ services:
|
|||||||
neta-storage:
|
neta-storage:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
environment:
|
environment:
|
||||||
NETA_MIGRATION_RUNNER_VERSION: "2026-06-14.2"
|
NETA_MIGRATION_RUNNER_VERSION: "2026-06-14.3"
|
||||||
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@neta-db:5432/postgres
|
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@neta-db:5432/postgres
|
||||||
NETA_POSTGREST_RELOAD_WAIT_SECONDS: ${NETA_POSTGREST_RELOAD_WAIT_SECONDS:-2}
|
NETA_POSTGREST_RELOAD_WAIT_SECONDS: ${NETA_POSTGREST_RELOAD_WAIT_SECONDS:-2}
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# 0011 - Service Role Claims And Storage Policies
|
||||||
|
|
||||||
|
SQL file:
|
||||||
|
|
||||||
|
`supabase/migrations/0011_fix_service_role_claims_and_storage_policies.sql`
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Fixes service-role detection for self-hosted PostgREST deployments and allows trusted server-side storage operations to pass RLS.
|
||||||
|
|
||||||
|
PostgREST stores JWT claims in `request.jwt.claims` JSON when legacy GUCs are disabled. Earlier SQL only checked the legacy `request.jwt.claim.role` value, so service-role requests could be rejected.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Adds `public.neta_current_jwt_role()`.
|
||||||
|
- Updates `public.request_internal_auth_creation()` to read both modern and legacy JWT claim formats.
|
||||||
|
- Recreates avatar and project asset storage policies to allow service-role operations.
|
||||||
|
- Reloads the PostgREST schema cache.
|
||||||
@@ -14,6 +14,7 @@ This file is the canonical order of SQL files for database setup and migration.
|
|||||||
| 0008 | `supabase/migrations/0008_add_project_progress_and_quota.sql` | `docs/database/0008-project-progress-and-quota.md` | Pending execution |
|
| 0008 | `supabase/migrations/0008_add_project_progress_and_quota.sql` | `docs/database/0008-project-progress-and-quota.md` | Pending execution |
|
||||||
| 0009 | `supabase/migrations/0009_lock_registration_after_first_admin.sql` | `docs/database/0009-lock-registration-after-first-admin.md` | Pending execution |
|
| 0009 | `supabase/migrations/0009_lock_registration_after_first_admin.sql` | `docs/database/0009-lock-registration-after-first-admin.md` | Pending execution |
|
||||||
| 0010 | `supabase/migrations/0010_allow_internal_auth_user_creation.sql` | `docs/database/0010-internal-auth-user-creation.md` | Pending execution |
|
| 0010 | `supabase/migrations/0010_allow_internal_auth_user_creation.sql` | `docs/database/0010-internal-auth-user-creation.md` | Pending execution |
|
||||||
|
| 0011 | `supabase/migrations/0011_fix_service_role_claims_and_storage_policies.sql` | `docs/database/0011-service-role-claims-and-storage-policies.md` | Pending execution |
|
||||||
| seed-0001 | `supabase/seeds/0001_demo_freelancer_os_data.sql` | `docs/database/seed-0001-demo-freelancer-os-data.md` | Optional demo seed, pending execution |
|
| seed-0001 | `supabase/seeds/0001_demo_freelancer_os_data.sql` | `docs/database/seed-0001-demo-freelancer-os-data.md` | Optional demo seed, pending execution |
|
||||||
|
|
||||||
## How To Add The Next Query
|
## How To Add The Next Query
|
||||||
@@ -38,4 +39,4 @@ Use the migration helper from the repository root:
|
|||||||
DATABASE_URL='postgresql://postgres:password@host:5432/postgres' sh ./scripts/apply-migrations.sh
|
DATABASE_URL='postgresql://postgres:password@host:5432/postgres' sh ./scripts/apply-migrations.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The helper applies missing queries from `0001` through `0010` in the order listed above and records completed migrations in `neta_internal.schema_migrations`. It uses local `psql` when available, otherwise it runs `psql` through Docker. After migrations, it sends `NOTIFY pgrst, 'reload schema'` so PostgREST can see new RPC functions without a manual restart.
|
The helper applies missing queries from `0001` through `0011` in the order listed above and records completed migrations in `neta_internal.schema_migrations`. It uses local `psql` when available, otherwise it runs `psql` through Docker. After migrations, it sends `NOTIFY pgrst, 'reload schema'` so PostgREST can see new RPC functions without a manual restart.
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ existing_objects_cover_migration() {
|
|||||||
0010_internal_auth_creation)
|
0010_internal_auth_creation)
|
||||||
echo "no"
|
echo "no"
|
||||||
;;
|
;;
|
||||||
|
0011_service_role_claims_storage)
|
||||||
|
echo "no"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "no"
|
echo "no"
|
||||||
;;
|
;;
|
||||||
@@ -165,6 +168,7 @@ done <<'SQL_FILES'
|
|||||||
0008_project_progress_quota|supabase/migrations/0008_add_project_progress_and_quota.sql
|
0008_project_progress_quota|supabase/migrations/0008_add_project_progress_and_quota.sql
|
||||||
0009_first_admin_registration_lock|supabase/migrations/0009_lock_registration_after_first_admin.sql
|
0009_first_admin_registration_lock|supabase/migrations/0009_lock_registration_after_first_admin.sql
|
||||||
0010_internal_auth_creation|supabase/migrations/0010_allow_internal_auth_user_creation.sql
|
0010_internal_auth_creation|supabase/migrations/0010_allow_internal_auth_user_creation.sql
|
||||||
|
0011_service_role_claims_storage|supabase/migrations/0011_fix_service_role_claims_and_storage_policies.sql
|
||||||
SQL_FILES
|
SQL_FILES
|
||||||
|
|
||||||
reload_postgrest_schema_cache
|
reload_postgrest_schema_cache
|
||||||
|
|||||||
+2
-1
@@ -37,7 +37,8 @@ Do not overwrite already executed SQL without also creating a new ordered migrat
|
|||||||
8. `migrations/0008_add_project_progress_and_quota.sql`
|
8. `migrations/0008_add_project_progress_and_quota.sql`
|
||||||
9. `migrations/0009_lock_registration_after_first_admin.sql`
|
9. `migrations/0009_lock_registration_after_first_admin.sql`
|
||||||
10. `migrations/0010_allow_internal_auth_user_creation.sql`
|
10. `migrations/0010_allow_internal_auth_user_creation.sql`
|
||||||
11. Optional local/demo data: `seeds/0001_demo_freelancer_os_data.sql`
|
11. `migrations/0011_fix_service_role_claims_and_storage_policies.sql`
|
||||||
|
12. Optional local/demo data: `seeds/0001_demo_freelancer_os_data.sql`
|
||||||
|
|
||||||
## Apply Migrations
|
## Apply Migrations
|
||||||
|
|
||||||
|
|||||||
@@ -89,26 +89,38 @@ drop policy if exists "Users can view their own project assets." on storage.obje
|
|||||||
create policy "Users can view their own project assets." on storage.objects
|
create policy "Users can view their own project assets." on storage.objects
|
||||||
for select using (
|
for select using (
|
||||||
bucket_id = 'project-assets'
|
bucket_id = 'project-assets'
|
||||||
and auth.uid()::text = (storage.foldername(name))[1]
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
drop policy if exists "Users can upload their own project assets." on storage.objects;
|
drop policy if exists "Users can upload their own project assets." on storage.objects;
|
||||||
create policy "Users can upload their own project assets." on storage.objects
|
create policy "Users can upload their own project assets." on storage.objects
|
||||||
for insert with check (
|
for insert with check (
|
||||||
bucket_id = 'project-assets'
|
bucket_id = 'project-assets'
|
||||||
and auth.uid()::text = (storage.foldername(name))[1]
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
drop policy if exists "Users can update their own project assets." on storage.objects;
|
drop policy if exists "Users can update their own project assets." on storage.objects;
|
||||||
create policy "Users can update their own project assets." on storage.objects
|
create policy "Users can update their own project assets." on storage.objects
|
||||||
for update using (
|
for update using (
|
||||||
bucket_id = 'project-assets'
|
bucket_id = 'project-assets'
|
||||||
and auth.uid()::text = (storage.foldername(name))[1]
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own project assets." on storage.objects;
|
drop policy if exists "Users can delete their own project assets." on storage.objects;
|
||||||
create policy "Users can delete their own project assets." on storage.objects
|
create policy "Users can delete their own project assets." on storage.objects
|
||||||
for delete using (
|
for delete using (
|
||||||
bucket_id = 'project-assets'
|
bucket_id = 'project-assets'
|
||||||
and auth.uid()::text = (storage.foldername(name))[1]
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,6 +17,23 @@ create index if not exists internal_auth_creations_email_idx
|
|||||||
revoke all on schema neta_internal from public;
|
revoke all on schema neta_internal from public;
|
||||||
revoke all on all tables in schema neta_internal from public;
|
revoke all on all tables in schema neta_internal from public;
|
||||||
|
|
||||||
|
create or replace function public.neta_current_jwt_role()
|
||||||
|
returns text
|
||||||
|
language sql
|
||||||
|
stable
|
||||||
|
as $$
|
||||||
|
select coalesce(
|
||||||
|
nullif(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role', ''),
|
||||||
|
nullif(current_setting('request.jwt.claim.role', true), ''),
|
||||||
|
''
|
||||||
|
);
|
||||||
|
$$;
|
||||||
|
|
||||||
|
revoke all on function public.neta_current_jwt_role() from public;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to anon;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to authenticated;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to service_role;
|
||||||
|
|
||||||
create or replace function public.request_internal_auth_creation(
|
create or replace function public.request_internal_auth_creation(
|
||||||
target_email text,
|
target_email text,
|
||||||
target_reason text default 'internal'
|
target_reason text default 'internal'
|
||||||
@@ -27,7 +44,7 @@ security definer
|
|||||||
set search_path = public, neta_internal
|
set search_path = public, neta_internal
|
||||||
as $$
|
as $$
|
||||||
begin
|
begin
|
||||||
if coalesce(current_setting('request.jwt.claim.role', true), '') <> 'service_role' then
|
if public.neta_current_jwt_role() <> 'service_role' then
|
||||||
raise exception 'Only service role can request internal auth creation.';
|
raise exception 'Only service role can request internal auth creation.';
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
-- 0011: Fix service-role JWT claim handling and storage policies
|
||||||
|
-- Run after: supabase/migrations/0010_allow_internal_auth_user_creation.sql
|
||||||
|
|
||||||
|
create schema if not exists neta_internal;
|
||||||
|
|
||||||
|
create table if not exists neta_internal.internal_auth_creations (
|
||||||
|
id uuid default uuid_generate_v4() primary key,
|
||||||
|
email text not null,
|
||||||
|
reason text default 'internal'::text not null,
|
||||||
|
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
|
||||||
|
expires_at timestamp with time zone default (timezone('utc'::text, now()) + interval '2 minutes') not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create index if not exists internal_auth_creations_email_idx
|
||||||
|
on neta_internal.internal_auth_creations (lower(email));
|
||||||
|
|
||||||
|
revoke all on schema neta_internal from public;
|
||||||
|
revoke all on all tables in schema neta_internal from public;
|
||||||
|
|
||||||
|
create or replace function public.neta_current_jwt_role()
|
||||||
|
returns text
|
||||||
|
language sql
|
||||||
|
stable
|
||||||
|
as $$
|
||||||
|
select coalesce(
|
||||||
|
nullif(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role', ''),
|
||||||
|
nullif(current_setting('request.jwt.claim.role', true), ''),
|
||||||
|
''
|
||||||
|
);
|
||||||
|
$$;
|
||||||
|
|
||||||
|
revoke all on function public.neta_current_jwt_role() from public;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to anon;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to authenticated;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to service_role;
|
||||||
|
|
||||||
|
create or replace function public.request_internal_auth_creation(
|
||||||
|
target_email text,
|
||||||
|
target_reason text default 'internal'
|
||||||
|
)
|
||||||
|
returns void
|
||||||
|
language plpgsql
|
||||||
|
security definer
|
||||||
|
set search_path = public, neta_internal
|
||||||
|
as $$
|
||||||
|
begin
|
||||||
|
if public.neta_current_jwt_role() <> 'service_role' then
|
||||||
|
raise exception 'Only service role can request internal auth creation.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if target_email is null or btrim(target_email) = '' then
|
||||||
|
raise exception 'target_email is required.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
delete from neta_internal.internal_auth_creations
|
||||||
|
where expires_at <= timezone('utc'::text, now())
|
||||||
|
or lower(email) = lower(btrim(target_email));
|
||||||
|
|
||||||
|
insert into neta_internal.internal_auth_creations (email, reason)
|
||||||
|
values (btrim(target_email), coalesce(nullif(btrim(target_reason), ''), 'internal'));
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
revoke all on function public.request_internal_auth_creation(text, text) from public;
|
||||||
|
grant execute on function public.request_internal_auth_creation(text, text) to service_role;
|
||||||
|
|
||||||
|
drop policy if exists "Avatar images are publicly accessible." on storage.objects;
|
||||||
|
create policy "Avatar images are publicly accessible."
|
||||||
|
on storage.objects for select
|
||||||
|
using (
|
||||||
|
bucket_id = 'avatars'
|
||||||
|
or public.neta_current_jwt_role() = 'service_role'
|
||||||
|
);
|
||||||
|
|
||||||
|
drop policy if exists "Users can upload an avatar." on storage.objects;
|
||||||
|
create policy "Users can upload an avatar."
|
||||||
|
on storage.objects for insert
|
||||||
|
with check (
|
||||||
|
bucket_id = 'avatars'
|
||||||
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
drop policy if exists "Users can update their own avatar." on storage.objects;
|
||||||
|
create policy "Users can update their own avatar."
|
||||||
|
on storage.objects for update
|
||||||
|
using (
|
||||||
|
bucket_id = 'avatars'
|
||||||
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
drop policy if exists "Users can delete their own avatar." on storage.objects;
|
||||||
|
create policy "Users can delete their own avatar."
|
||||||
|
on storage.objects for delete
|
||||||
|
using (
|
||||||
|
bucket_id = 'avatars'
|
||||||
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
drop policy if exists "Users can view their own project assets." on storage.objects;
|
||||||
|
create policy "Users can view their own project assets." on storage.objects
|
||||||
|
for select using (
|
||||||
|
bucket_id = 'project-assets'
|
||||||
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
drop policy if exists "Users can upload their own project assets." on storage.objects;
|
||||||
|
create policy "Users can upload their own project assets." on storage.objects
|
||||||
|
for insert with check (
|
||||||
|
bucket_id = 'project-assets'
|
||||||
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
drop policy if exists "Users can update their own project assets." on storage.objects;
|
||||||
|
create policy "Users can update their own project assets." on storage.objects
|
||||||
|
for update using (
|
||||||
|
bucket_id = 'project-assets'
|
||||||
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
drop policy if exists "Users can delete their own project assets." on storage.objects;
|
||||||
|
create policy "Users can delete their own project assets." on storage.objects
|
||||||
|
for delete using (
|
||||||
|
bucket_id = 'project-assets'
|
||||||
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
notify pgrst, 'reload schema';
|
||||||
+34
-5
@@ -193,6 +193,23 @@ revoke all on function public.is_first_admin_setup_available() from public;
|
|||||||
grant execute on function public.is_first_admin_setup_available() to anon;
|
grant execute on function public.is_first_admin_setup_available() to anon;
|
||||||
grant execute on function public.is_first_admin_setup_available() to authenticated;
|
grant execute on function public.is_first_admin_setup_available() to authenticated;
|
||||||
|
|
||||||
|
create or replace function public.neta_current_jwt_role()
|
||||||
|
returns text
|
||||||
|
language sql
|
||||||
|
stable
|
||||||
|
as $$
|
||||||
|
select coalesce(
|
||||||
|
nullif(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role', ''),
|
||||||
|
nullif(current_setting('request.jwt.claim.role', true), ''),
|
||||||
|
''
|
||||||
|
);
|
||||||
|
$$;
|
||||||
|
|
||||||
|
revoke all on function public.neta_current_jwt_role() from public;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to anon;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to authenticated;
|
||||||
|
grant execute on function public.neta_current_jwt_role() to service_role;
|
||||||
|
|
||||||
create schema if not exists neta_internal;
|
create schema if not exists neta_internal;
|
||||||
|
|
||||||
create table if not exists neta_internal.internal_auth_creations (
|
create table if not exists neta_internal.internal_auth_creations (
|
||||||
@@ -219,7 +236,7 @@ security definer
|
|||||||
set search_path = public, neta_internal
|
set search_path = public, neta_internal
|
||||||
as $$
|
as $$
|
||||||
begin
|
begin
|
||||||
if coalesce(current_setting('request.jwt.claim.role', true), '') <> 'service_role' then
|
if public.neta_current_jwt_role() <> 'service_role' then
|
||||||
raise exception 'Only service role can request internal auth creation.';
|
raise exception 'Only service role can request internal auth creation.';
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
@@ -285,14 +302,20 @@ on conflict (id) do nothing;
|
|||||||
drop policy if exists "Avatar images are publicly accessible." on storage.objects;
|
drop policy if exists "Avatar images are publicly accessible." on storage.objects;
|
||||||
create policy "Avatar images are publicly accessible."
|
create policy "Avatar images are publicly accessible."
|
||||||
on storage.objects for select
|
on storage.objects for select
|
||||||
using (bucket_id = 'avatars');
|
using (
|
||||||
|
bucket_id = 'avatars'
|
||||||
|
or public.neta_current_jwt_role() = 'service_role'
|
||||||
|
);
|
||||||
|
|
||||||
drop policy if exists "Users can upload an avatar." on storage.objects;
|
drop policy if exists "Users can upload an avatar." on storage.objects;
|
||||||
create policy "Users can upload an avatar."
|
create policy "Users can upload an avatar."
|
||||||
on storage.objects for insert
|
on storage.objects for insert
|
||||||
with check (
|
with check (
|
||||||
bucket_id = 'avatars'
|
bucket_id = 'avatars'
|
||||||
and auth.uid()::text = (storage.foldername(name))[1]
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
drop policy if exists "Users can update their own avatar." on storage.objects;
|
drop policy if exists "Users can update their own avatar." on storage.objects;
|
||||||
@@ -300,7 +323,10 @@ create policy "Users can update their own avatar."
|
|||||||
on storage.objects for update
|
on storage.objects for update
|
||||||
using (
|
using (
|
||||||
bucket_id = 'avatars'
|
bucket_id = 'avatars'
|
||||||
and auth.uid()::text = (storage.foldername(name))[1]
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
drop policy if exists "Users can delete their own avatar." on storage.objects;
|
drop policy if exists "Users can delete their own avatar." on storage.objects;
|
||||||
@@ -308,5 +334,8 @@ create policy "Users can delete their own avatar."
|
|||||||
on storage.objects for delete
|
on storage.objects for delete
|
||||||
using (
|
using (
|
||||||
bucket_id = 'avatars'
|
bucket_id = 'avatars'
|
||||||
and auth.uid()::text = (storage.foldername(name))[1]
|
and (
|
||||||
|
public.neta_current_jwt_role() = 'service_role'
|
||||||
|
or auth.uid()::text = (storage.foldername(name))[1]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user