feat: implement row-level security policies for course-related tables and actions

This commit is contained in:
Rotimi Best
2025-08-08 04:12:46 +01:00
parent 195e5e8fd2
commit 0d0719e8f9
3 changed files with 392 additions and 5 deletions
+1 -4
View File
@@ -9,10 +9,7 @@
"format": "prettier . --write",
"release": "standard-version",
"ci": "cypress run",
"supabase:start": "pnpx supabase@1.51.0 start",
"supabase:login": "pnpx supabase@1.51.0 login",
"supabase:status": "pnpx supabase@1.51.0 status",
"supabase": "pnpx supabase@1.51.0",
"supabase": "pnpx supabase",
"dashboard:build": "cd apps/dashboard && pnpm i && IS_SELFHOSTED=true NODE_OPTIONS='--max-old-space-size=20384' DEPLOYMENT_PROVIDER=docker pnpm build",
"dashboard:start": "cd apps/dashboard && node build"
},
@@ -0,0 +1,390 @@
drop policy "Enable access to all users" on "public"."course";
drop policy "User must be an course member to INSERT" on "public"."lesson_completion";
drop policy "User must be a course member to INSERT" on "public"."apps_poll";
drop policy "User must be course member to SELECT" on "public"."apps_poll";
drop policy "User must be a course member to INSERT" on "public"."apps_poll_option";
drop policy "User must be a course member to UPDATE" on "public"."apps_poll_option";
drop policy "User must be a teacher to DELETE" on "public"."apps_poll_option";
drop policy "User must be course member to SELECT" on "public"."apps_poll_option";
drop policy "User must be a course member to INSERT" on "public"."course_newsfeed";
drop policy "User must be a course member to SELECT" on "public"."course_newsfeed";
drop policy "User must be a course member to INSERT" on "public"."course_newsfeed_comment";
drop policy "User must be a course member to SELECT" on "public"."course_newsfeed_comment";
drop policy "User must be a course member to INSERT" on "public"."group_attendance";
drop policy "User must be a course member to SELECT" on "public"."group_attendance";
drop policy "User must be a course member to UPDATE" on "public"."group_attendance";
drop policy "User must be in course group to INSERT" on "public"."lesson_comment";
drop policy "User must be an course member to DELETE" on "public"."question_answer";
drop policy "User must be an course member to INSERT" on "public"."question_answer";
drop policy "User must be an course member to UPDATE" on "public"."question_answer";
drop policy "User must be a course member to DELETE" on "public"."submission";
drop policy "User must be a course member to INSERT" on "public"."submission";
drop policy "User must be a course member to UPDATE" on "public"."submission";
drop function if exists "public"."is_user_in_group_with_role"(group_id integer);
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.is_user_in_course_group_or_admin(group_id uuid)
RETURNS boolean
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
DECLARE
org_id uuid;
is_admin boolean;
is_member boolean;
BEGIN
SELECT organization_id INTO org_id FROM "group" WHERE id = $1 LIMIT 1;
is_admin := is_org_admin(org_id);
is_member := is_user_in_course_group($1);
RETURN is_admin OR is_member;
END;
$function$
;
create policy "Enable access to all users if PUBLIC or to course members when "
on "public"."course"
as permissive
for select
to public
using ((is_published OR is_user_in_course_group_or_admin(group_id)));
create policy "User must be an course member or Admin to perform ALL operation"
on "public"."lesson_completion"
as permissive
for all
to public
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT lesson.course_id
FROM lesson
WHERE (lesson.id = lesson_completion.lesson_id)
LIMIT 1))
LIMIT 1)));
create policy "User must be an admin to INSERT or allow if no existing member"
on "public"."organizationmember"
as permissive
for insert
to public
with check ((is_org_admin() OR (NOT (EXISTS ( SELECT 1
FROM organizationmember organizationmember_1
WHERE (organizationmember_1.organization_id = organizationmember_1.organization_id))))));
create policy "User must be a course member to INSERT"
on "public"."apps_poll"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT groupmember.group_id
FROM groupmember
WHERE (groupmember.id = apps_poll."authorId")
LIMIT 1)));
create policy "User must be course member to SELECT"
on "public"."apps_poll"
as permissive
for select
to public
using (is_user_in_course_group_or_admin(( SELECT groupmember.group_id
FROM groupmember
WHERE (groupmember.id = apps_poll."authorId")
LIMIT 1)));
create policy "User must be a course member to INSERT"
on "public"."apps_poll_option"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT groupmember.group_id
FROM groupmember
WHERE (groupmember.id = ( SELECT apps_poll."authorId"
FROM apps_poll
WHERE (apps_poll.id = apps_poll_option.poll_id)
LIMIT 1))
LIMIT 1)));
create policy "User must be a course member to UPDATE"
on "public"."apps_poll_option"
as permissive
for update
to public
using (is_user_in_course_group_or_admin(( SELECT groupmember.group_id
FROM groupmember
WHERE (groupmember.id = ( SELECT apps_poll."authorId"
FROM apps_poll
WHERE (apps_poll.id = apps_poll_option.poll_id)
LIMIT 1))
LIMIT 1)))
with check (is_user_in_course_group_or_admin(( SELECT groupmember.group_id
FROM groupmember
WHERE (groupmember.id = ( SELECT apps_poll."authorId"
FROM apps_poll
WHERE (apps_poll.id = apps_poll_option.poll_id)
LIMIT 1))
LIMIT 1)));
create policy "User must be a teacher to DELETE"
on "public"."apps_poll_option"
as permissive
for delete
to public
using (is_user_in_course_group_or_admin(( SELECT groupmember.group_id
FROM groupmember
WHERE (groupmember.id = ( SELECT apps_poll."authorId"
FROM apps_poll
WHERE (apps_poll.id = apps_poll_option.poll_id)
LIMIT 1))
LIMIT 1)));
create policy "User must be course member to SELECT"
on "public"."apps_poll_option"
as permissive
for select
to public
using (is_user_in_course_group_or_admin(( SELECT groupmember.group_id
FROM groupmember
WHERE (groupmember.id = ( SELECT apps_poll."authorId"
FROM apps_poll
WHERE (apps_poll.id = apps_poll_option.poll_id)
LIMIT 1))
LIMIT 1)));
create policy "User must be a course member to INSERT"
on "public"."course_newsfeed"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = course_newsfeed.course_id)
LIMIT 1)));
create policy "User must be a course member to SELECT"
on "public"."course_newsfeed"
as permissive
for select
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = course_newsfeed.course_id)
LIMIT 1)));
create policy "User must be a course member to INSERT"
on "public"."course_newsfeed_comment"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT course_newsfeed.course_id
FROM course_newsfeed
WHERE (course_newsfeed.id = course_newsfeed_comment.course_newsfeed_id)))
LIMIT 1)));
create policy "User must be a course member to SELECT"
on "public"."course_newsfeed_comment"
as permissive
for select
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT course_newsfeed.course_id
FROM course_newsfeed
WHERE (course_newsfeed.id = course_newsfeed_comment.course_newsfeed_id)))
LIMIT 1)));
create policy "User must be a course member to INSERT"
on "public"."group_attendance"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = group_attendance.course_id)
LIMIT 1)));
create policy "User must be a course member to SELECT"
on "public"."group_attendance"
as permissive
for select
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = group_attendance.course_id)
LIMIT 1)));
create policy "User must be a course member to UPDATE"
on "public"."group_attendance"
as permissive
for update
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = group_attendance.course_id)
LIMIT 1)))
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = group_attendance.course_id)
LIMIT 1)));
create policy "User must be in course group to INSERT"
on "public"."lesson_comment"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT lesson.course_id
FROM lesson
WHERE (lesson.id = lesson_comment.lesson_id)
LIMIT 1))
LIMIT 1)));
create policy "User must be an course member to DELETE"
on "public"."question_answer"
as permissive
for delete
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT lesson.course_id
FROM lesson
WHERE (lesson.id = ( SELECT exercise.lesson_id
FROM exercise
WHERE (exercise.id = ( SELECT question.exercise_id
FROM question
WHERE (question.id = question_answer.question_id)
LIMIT 1))
LIMIT 1))
LIMIT 1))
LIMIT 1)));
create policy "User must be an course member to INSERT"
on "public"."question_answer"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT lesson.course_id
FROM lesson
WHERE (lesson.id = ( SELECT exercise.lesson_id
FROM exercise
WHERE (exercise.id = ( SELECT question.exercise_id
FROM question
WHERE (question.id = question_answer.question_id)
LIMIT 1))
LIMIT 1))
LIMIT 1))
LIMIT 1)));
create policy "User must be an course member to UPDATE"
on "public"."question_answer"
as permissive
for update
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT lesson.course_id
FROM lesson
WHERE (lesson.id = ( SELECT exercise.lesson_id
FROM exercise
WHERE (exercise.id = ( SELECT question.exercise_id
FROM question
WHERE (question.id = question_answer.question_id)
LIMIT 1))
LIMIT 1))
LIMIT 1))
LIMIT 1)))
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = ( SELECT lesson.course_id
FROM lesson
WHERE (lesson.id = ( SELECT exercise.lesson_id
FROM exercise
WHERE (exercise.id = ( SELECT question.exercise_id
FROM question
WHERE (question.id = question_answer.question_id)
LIMIT 1))
LIMIT 1))
LIMIT 1))
LIMIT 1)));
create policy "User must be a course member to DELETE"
on "public"."submission"
as permissive
for delete
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = submission.course_id)
LIMIT 1)));
create policy "User must be a course member to INSERT"
on "public"."submission"
as permissive
for insert
to public
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = submission.course_id)
LIMIT 1)));
create policy "User must be a course member to UPDATE"
on "public"."submission"
as permissive
for update
to public
using (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = submission.course_id)
LIMIT 1)))
with check (is_user_in_course_group_or_admin(( SELECT course.group_id
FROM course
WHERE (course.id = submission.course_id)
LIMIT 1)));
+1 -1
View File
@@ -19,7 +19,7 @@ INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encryp
-- Data for Name: identities; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin
--
INSERT INTO "auth"."identities" ("id", "user_id", "identity_data", "provider", "last_sign_in_at", "created_at", "updated_at") VALUES
INSERT INTO "auth"."identities" ("provider_id", "user_id", "identity_data", "provider", "last_sign_in_at", "created_at", "updated_at") VALUES
('01676a50-bb56-4c5e-8a61-fb9e9190fb10', '01676a50-bb56-4c5e-8a61-fb9e9190fb10', '{"sub": "01676a50-bb56-4c5e-8a61-fb9e9190fb10", "email": "test@test.com"}', 'email', '2023-11-22 10:09:32.225444+00', '2023-11-22 10:09:32.225521+00', '2023-11-22 10:09:32.225521+00'),
('7ac00503-8519-43c8-a5ea-b79aeca900b1', '7ac00503-8519-43c8-a5ea-b79aeca900b1', '{"sub": "7ac00503-8519-43c8-a5ea-b79aeca900b1", "email": "admin@test.com"}', 'email', '2023-12-16 14:04:38.302264+00', '2023-12-16 14:04:38.302326+00', '2023-12-16 14:04:38.302326+00'),
('0c256e75-aa40-4f62-8d30-0217ca1c60d9', '0c256e75-aa40-4f62-8d30-0217ca1c60d9', '{"sub": "0c256e75-aa40-4f62-8d30-0217ca1c60d9", "email": "student@test.com"}', 'email', '2023-12-18 17:08:57.410285+00', '2023-12-18 17:08:57.410344+00', '2023-12-18 17:08:57.410344+00');