00002_rls_policies.sql 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. -- 00002_rls_policies.sql
  2. -- MovieDice: Row Level Security policies for all tables
  3. -- =============================================================================
  4. -- Enable RLS on all tables
  5. -- =============================================================================
  6. ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
  7. ALTER TABLE public.groups ENABLE ROW LEVEL SECURITY;
  8. ALTER TABLE public.group_members ENABLE ROW LEVEL SECURITY;
  9. ALTER TABLE public.movies ENABLE ROW LEVEL SECURITY;
  10. ALTER TABLE public.landing_reel_posters ENABLE ROW LEVEL SECURITY;
  11. -- =============================================================================
  12. -- users: SELECT/UPDATE own row only
  13. -- =============================================================================
  14. CREATE POLICY users_select_own ON public.users
  15. FOR SELECT USING (auth.uid() = id);
  16. CREATE POLICY users_update_own ON public.users
  17. FOR UPDATE USING (auth.uid() = id)
  18. WITH CHECK (auth.uid() = id);
  19. -- =============================================================================
  20. -- groups: SELECT only if user is a member
  21. -- =============================================================================
  22. CREATE POLICY groups_select_member ON public.groups
  23. FOR SELECT USING (
  24. EXISTS (
  25. SELECT 1 FROM public.group_members
  26. WHERE group_members.group_id = groups.id
  27. AND group_members.user_id = auth.uid()
  28. )
  29. );
  30. -- =============================================================================
  31. -- group_members: complex policies
  32. -- =============================================================================
  33. -- SELECT: can see members of groups you belong to
  34. CREATE POLICY group_members_select ON public.group_members
  35. FOR SELECT USING (
  36. EXISTS (
  37. SELECT 1 FROM public.group_members AS gm
  38. WHERE gm.group_id = group_members.group_id
  39. AND gm.user_id = auth.uid()
  40. )
  41. );
  42. -- INSERT: blocked for anon users (server-side via service role key only)
  43. -- No INSERT policy = denied by default with RLS enabled
  44. -- DELETE: admin of the group OR self (leaving)
  45. CREATE POLICY group_members_delete ON public.group_members
  46. FOR DELETE USING (
  47. group_members.user_id = auth.uid()
  48. OR EXISTS (
  49. SELECT 1 FROM public.group_members AS gm
  50. WHERE gm.group_id = group_members.group_id
  51. AND gm.user_id = auth.uid()
  52. AND gm.role = 'admin'
  53. )
  54. );
  55. -- UPDATE: prevent role escalation
  56. -- Only admins of the group can update roles, and only they can set role to 'admin'
  57. CREATE POLICY group_members_update ON public.group_members
  58. FOR UPDATE USING (
  59. EXISTS (
  60. SELECT 1 FROM public.group_members AS gm
  61. WHERE gm.group_id = group_members.group_id
  62. AND gm.user_id = auth.uid()
  63. AND gm.role = 'admin'
  64. )
  65. )
  66. WITH CHECK (
  67. -- Only allow setting role to 'admin' if the current user is an admin of this group
  68. role = 'member'
  69. OR EXISTS (
  70. SELECT 1 FROM public.group_members AS gm
  71. WHERE gm.group_id = group_members.group_id
  72. AND gm.user_id = auth.uid()
  73. AND gm.role = 'admin'
  74. )
  75. );
  76. -- =============================================================================
  77. -- movies: full CRUD for group members
  78. -- =============================================================================
  79. -- SELECT: only if member of the owning group
  80. CREATE POLICY movies_select ON public.movies
  81. FOR SELECT USING (
  82. EXISTS (
  83. SELECT 1 FROM public.group_members
  84. WHERE group_members.group_id = movies.group_id
  85. AND group_members.user_id = auth.uid()
  86. )
  87. );
  88. -- INSERT: member of group + added_by must be auth.uid()
  89. CREATE POLICY movies_insert ON public.movies
  90. FOR INSERT WITH CHECK (
  91. added_by = auth.uid()
  92. AND EXISTS (
  93. SELECT 1 FROM public.group_members
  94. WHERE group_members.group_id = movies.group_id
  95. AND group_members.user_id = auth.uid()
  96. )
  97. );
  98. -- UPDATE: member of group + cannot change added_by
  99. CREATE POLICY movies_update ON public.movies
  100. FOR UPDATE USING (
  101. EXISTS (
  102. SELECT 1 FROM public.group_members
  103. WHERE group_members.group_id = movies.group_id
  104. AND group_members.user_id = auth.uid()
  105. )
  106. )
  107. WITH CHECK (
  108. added_by IS NOT DISTINCT FROM (
  109. SELECT m.added_by FROM public.movies m WHERE m.id = movies.id
  110. )
  111. AND EXISTS (
  112. SELECT 1 FROM public.group_members
  113. WHERE group_members.group_id = movies.group_id
  114. AND group_members.user_id = auth.uid()
  115. )
  116. );
  117. -- DELETE: member of the owning group
  118. CREATE POLICY movies_delete ON public.movies
  119. FOR DELETE USING (
  120. EXISTS (
  121. SELECT 1 FROM public.group_members
  122. WHERE group_members.group_id = movies.group_id
  123. AND group_members.user_id = auth.uid()
  124. )
  125. );
  126. -- =============================================================================
  127. -- landing_reel_posters: public read, no client write
  128. -- =============================================================================
  129. CREATE POLICY landing_reel_posters_select ON public.landing_reel_posters
  130. FOR SELECT USING (true);
  131. -- No INSERT/UPDATE/DELETE policies = service role only (RLS bypassed by service role key)