full-stack-school/supabase/migrations/20260227004420_supabase_rls.sql
2026-03-01 18:32:49 +00:00

202 lines
8.7 KiB
PL/PgSQL

-- 1. Enable RLS on all tables
DO $$ BEGIN
ALTER TABLE "Admin" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Student" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Teacher" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Parent" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Grade" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Class" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Subject" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Lesson" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Exam" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Assignment" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Result" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Attendance" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Event" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Announcement" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "_SubjectToTeacher" ENABLE ROW LEVEL SECURITY;
EXCEPTION
WHEN undefined_table THEN
NULL;
END $$;
-- 2. Utility helper functions connecting to Clerk JWT
CREATE OR REPLACE FUNCTION requesting_user_id()
RETURNS text
LANGUAGE sql STABLE
AS $$
SELECT NULLIF(current_setting('request.jwt.claims', true)::json->>'sub', '')::text;
$$;
CREATE OR REPLACE FUNCTION requesting_user_role()
RETURNS text
LANGUAGE sql STABLE
AS $$
SELECT current_setting('request.jwt.claims', true)::json->>'role';
$$;
CREATE OR REPLACE FUNCTION is_admin() RETURNS boolean LANGUAGE sql STABLE AS $$
SELECT requesting_user_role() = 'admin';
$$;
-- 3. Bypass-RLS Helper Functions for finding accessible IDs
-- We use SECURITY DEFINER SET search_path = public to execute these queries as the database owner,
-- which prevents infinite recursion errors (stack depth limit exceeded) when querying tables that have RLS enabled.
-- Transaction-local variables like `request.jwt.claims` perfectly persist through SECURITY DEFINER calls.
CREATE OR REPLACE FUNCTION auth_user_classes() RETURNS SETOF integer LANGUAGE plpgsql SECURITY DEFINER SET search_path = public STABLE AS $$
BEGIN
RETURN QUERY
SELECT "classId" FROM "Student" WHERE id = requesting_user_id() AND requesting_user_role() = 'student'
UNION
SELECT "classId" FROM "Student" WHERE "parentId" = requesting_user_id() AND requesting_user_role() = 'parent'
UNION
SELECT "classId" FROM "Lesson" WHERE "teacherId" = requesting_user_id() AND requesting_user_role() = 'teacher'
UNION
SELECT id FROM "Class" WHERE "supervisorId" = requesting_user_id() AND requesting_user_role() = 'teacher';
END;
$$;
CREATE OR REPLACE FUNCTION auth_user_students() RETURNS SETOF text LANGUAGE plpgsql SECURITY DEFINER SET search_path = public STABLE AS $$
BEGIN
RETURN QUERY
SELECT id FROM "Student" WHERE id = requesting_user_id() AND requesting_user_role() = 'student'
UNION
SELECT id FROM "Student" WHERE "parentId" = requesting_user_id() AND requesting_user_role() = 'parent'
UNION
SELECT id FROM "Student" WHERE "classId" IN (
SELECT "classId" FROM "Lesson" WHERE "teacherId" = requesting_user_id()
) AND requesting_user_role() = 'teacher';
END;
$$;
CREATE OR REPLACE FUNCTION auth_user_lessons() RETURNS SETOF integer LANGUAGE plpgsql SECURITY DEFINER SET search_path = public STABLE AS $$
BEGIN
RETURN QUERY
SELECT id FROM "Lesson" WHERE "teacherId" = requesting_user_id() AND requesting_user_role() = 'teacher'
UNION
SELECT id FROM "Lesson" WHERE "classId" IN (
SELECT "classId" FROM "Student" WHERE id = requesting_user_id() AND requesting_user_role() = 'student'
)
UNION
SELECT id FROM "Lesson" WHERE "classId" IN (
SELECT "classId" FROM "Student" WHERE "parentId" = requesting_user_id() AND requesting_user_role() = 'parent'
);
END;
$$;
CREATE OR REPLACE FUNCTION auth_user_subjects() RETURNS SETOF integer LANGUAGE plpgsql SECURITY DEFINER SET search_path = public STABLE AS $$
BEGIN
RETURN QUERY
SELECT "A" FROM "_SubjectToTeacher" WHERE "B" = requesting_user_id() AND requesting_user_role() = 'teacher'
UNION
SELECT "subjectId" FROM "Lesson" WHERE "classId" IN (
SELECT "classId" FROM "Student" WHERE id = requesting_user_id() AND requesting_user_role() = 'student'
)
UNION
SELECT "subjectId" FROM "Lesson" WHERE "classId" IN (
SELECT "classId" FROM "Student" WHERE "parentId" = requesting_user_id() AND requesting_user_role() = 'parent'
);
END;
$$;
CREATE OR REPLACE FUNCTION auth_user_teachers() RETURNS SETOF text LANGUAGE plpgsql SECURITY DEFINER SET search_path = public STABLE AS $$
BEGIN
RETURN QUERY
SELECT id FROM "Teacher" WHERE id = requesting_user_id() AND requesting_user_role() = 'teacher'
UNION
SELECT "teacherId" FROM "Lesson" WHERE "classId" IN (
SELECT "classId" FROM "Student" WHERE id = requesting_user_id() AND requesting_user_role() = 'student'
)
UNION
SELECT "teacherId" FROM "Lesson" WHERE "classId" IN (
SELECT "classId" FROM "Student" WHERE "parentId" = requesting_user_id() AND requesting_user_role() = 'parent'
);
END;
$$;
CREATE OR REPLACE FUNCTION auth_user_parents() RETURNS SETOF text LANGUAGE plpgsql SECURITY DEFINER SET search_path = public STABLE AS $$
BEGIN
RETURN QUERY
SELECT id FROM "Parent" WHERE id = requesting_user_id() AND requesting_user_role() = 'parent'
UNION
SELECT "parentId" FROM "Student" WHERE id = requesting_user_id() AND requesting_user_role() = 'student';
END;
$$;
-- 4. Define Table Policies using Helpers
CREATE POLICY "Admin has full access" ON "Admin" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Student" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Teacher" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Parent" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Grade" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Class" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Subject" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Lesson" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Exam" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Assignment" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Result" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Attendance" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Event" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "Announcement" FOR ALL USING (is_admin());
CREATE POLICY "Admins have full access" ON "_SubjectToTeacher" FOR ALL USING (is_admin());
CREATE POLICY "Anyone can view grades" ON "Grade" FOR SELECT TO authenticated USING (true);
CREATE POLICY "Anyone can view subject-teacher" ON "_SubjectToTeacher" FOR SELECT TO authenticated USING (true);
CREATE POLICY "Users can view permitted students" ON "Student" FOR SELECT USING (
is_admin() OR id IN (SELECT auth_user_students())
);
CREATE POLICY "Users can view permitted teachers" ON "Teacher" FOR SELECT USING (
is_admin() OR id IN (SELECT auth_user_teachers())
);
CREATE POLICY "Users can view permitted parents" ON "Parent" FOR SELECT USING (
is_admin() OR id IN (SELECT auth_user_parents())
);
CREATE POLICY "Users can view permitted subjects" ON "Subject" FOR SELECT USING (
is_admin() OR id IN (SELECT auth_user_subjects())
);
CREATE POLICY "Users can view permitted classes" ON "Class" FOR SELECT USING (
is_admin() OR id IN (SELECT auth_user_classes())
);
CREATE POLICY "Users can view permitted lessons" ON "Lesson" FOR SELECT USING (
is_admin() OR id IN (SELECT auth_user_lessons())
);
CREATE POLICY "Users can view permitted exams" ON "Exam" FOR SELECT USING (
is_admin() OR "lessonId" IN (SELECT auth_user_lessons())
);
CREATE POLICY "Users can view permitted assignments" ON "Assignment" FOR SELECT USING (
is_admin() OR "lessonId" IN (SELECT auth_user_lessons())
);
CREATE POLICY "Users can view permitted results" ON "Result" FOR SELECT USING (
is_admin() OR "studentId" IN (SELECT auth_user_students()) OR (
requesting_user_role() = 'teacher' AND (
"examId" IN (SELECT id FROM "Exam" WHERE "lessonId" IN (SELECT auth_user_lessons())) OR
"assignmentId" IN (SELECT id FROM "Assignment" WHERE "lessonId" IN (SELECT auth_user_lessons()))
)
)
);
CREATE POLICY "Users can view permitted attendance" ON "Attendance" FOR SELECT USING (
is_admin() OR "studentId" IN (SELECT auth_user_students())
);
CREATE POLICY "Users can view permitted events" ON "Event" FOR SELECT USING (
is_admin() OR "classId" IS NULL OR "classId" IN (SELECT auth_user_classes())
);
CREATE POLICY "Users can view permitted announcements" ON "Announcement" FOR SELECT USING (
is_admin() OR "classId" IS NULL OR "classId" IN (SELECT auth_user_classes())
);