Skip to content

Commit 7b382d3

Browse files
feat: add row-level security policies and access functions for projects and canvases (#1969)
* feat: add row-level security policies and access functions for projects and canvases - Implemented helper functions `user_has_project_access` and `user_has_canvas_access` to check user roles. - Created row-level security policies for `projects`, `canvas`, `user_projects`, `user_canvases`, `frames`, `users`, `user_settings`, and `project_invitations` to enforce access control based on user roles. - Ensured only authenticated users can perform actions based on their roles in the respective tables. * chore: make migrations idempotent
1 parent bc3cec7 commit 7b382d3

File tree

3 files changed

+1061
-0
lines changed

3 files changed

+1061
-0
lines changed
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
-- Helper function to check if user has specific roles for a project
2+
CREATE OR REPLACE FUNCTION user_has_project_access(
3+
project_id_param UUID,
4+
required_roles TEXT[]
5+
) RETURNS BOOLEAN AS $$
6+
BEGIN
7+
RETURN EXISTS (
8+
SELECT 1 FROM user_projects
9+
WHERE user_projects.project_id = project_id_param
10+
AND user_projects.user_id = auth.uid()
11+
AND user_projects.role = ANY(required_roles)
12+
);
13+
END;
14+
$$ LANGUAGE plpgsql SECURITY DEFINER;
15+
16+
-- Helper function to check if user has project access via canvas
17+
CREATE OR REPLACE FUNCTION user_has_canvas_access(
18+
canvas_id_param UUID,
19+
required_roles TEXT[]
20+
) RETURNS BOOLEAN AS $$
21+
BEGIN
22+
RETURN EXISTS (
23+
SELECT 1 FROM canvas c
24+
JOIN user_projects up ON c.project_id = up.project_id
25+
WHERE c.id = canvas_id_param
26+
AND up.user_id = auth.uid()
27+
AND up.role = ANY(required_roles)
28+
);
29+
END;
30+
$$ LANGUAGE plpgsql SECURITY DEFINER;
31+
32+
-- PROJECTS POLICIES
33+
DROP POLICY IF EXISTS "projects_insert_policy" ON projects;
34+
CREATE POLICY "projects_insert_policy" ON projects
35+
FOR INSERT
36+
TO authenticated
37+
WITH CHECK (true);
38+
39+
DROP POLICY IF EXISTS "projects_select_policy" ON projects;
40+
CREATE POLICY "projects_select_policy" ON projects
41+
FOR SELECT
42+
TO authenticated
43+
USING (user_has_project_access(projects.id, ARRAY['owner', 'admin']));
44+
45+
DROP POLICY IF EXISTS "projects_update_policy" ON projects;
46+
CREATE POLICY "projects_update_policy" ON projects
47+
FOR UPDATE
48+
TO authenticated
49+
USING (user_has_project_access(projects.id, ARRAY['owner', 'admin']));
50+
51+
DROP POLICY IF EXISTS "projects_delete_policy" ON projects;
52+
CREATE POLICY "projects_delete_policy" ON projects
53+
FOR DELETE
54+
TO authenticated
55+
USING (user_has_project_access(projects.id, ARRAY['owner']));
56+
57+
-- CANVAS POLICIES
58+
DROP POLICY IF EXISTS "canvas_insert_policy" ON canvas;
59+
CREATE POLICY "canvas_insert_policy" ON canvas
60+
FOR INSERT
61+
TO authenticated
62+
WITH CHECK (user_has_project_access(canvas.project_id, ARRAY['owner']));
63+
64+
DROP POLICY IF EXISTS "canvas_select_policy" ON canvas;
65+
CREATE POLICY "canvas_select_policy" ON canvas
66+
FOR SELECT
67+
TO authenticated
68+
USING (user_has_project_access(canvas.project_id, ARRAY['owner', 'admin']));
69+
70+
DROP POLICY IF EXISTS "canvas_update_policy" ON canvas;
71+
CREATE POLICY "canvas_update_policy" ON canvas
72+
FOR UPDATE
73+
TO authenticated
74+
USING (user_has_project_access(canvas.project_id, ARRAY['owner', 'admin']))
75+
WITH CHECK (user_has_project_access(canvas.project_id, ARRAY['owner', 'admin']));
76+
77+
DROP POLICY IF EXISTS "canvas_delete_policy" ON canvas;
78+
CREATE POLICY "canvas_delete_policy" ON canvas
79+
FOR DELETE
80+
TO authenticated
81+
USING (user_has_project_access(canvas.project_id, ARRAY['owner']));
82+
83+
-- USER_PROJECTS POLICIES
84+
DROP POLICY IF EXISTS "user_projects_select_policy" ON user_projects;
85+
CREATE POLICY "user_projects_select_policy" ON user_projects
86+
FOR SELECT
87+
TO authenticated
88+
USING (
89+
auth.uid() = user_projects.user_id OR
90+
user_has_project_access(user_projects.project_id, ARRAY['owner', 'admin'])
91+
);
92+
93+
DROP POLICY IF EXISTS "user_projects_update_policy" ON user_projects;
94+
CREATE POLICY "user_projects_update_policy" ON user_projects
95+
FOR UPDATE
96+
TO authenticated
97+
USING (user_has_project_access(user_projects.project_id, ARRAY['owner', 'admin']));
98+
99+
DROP POLICY IF EXISTS "user_projects_delete_policy" ON user_projects;
100+
CREATE POLICY "user_projects_delete_policy" ON user_projects
101+
FOR DELETE
102+
TO authenticated
103+
USING (
104+
auth.uid() = user_projects.user_id OR
105+
user_has_project_access(user_projects.project_id, ARRAY['owner'])
106+
);
107+
108+
-- USER_CANVASES POLICIES
109+
DROP POLICY IF EXISTS "user_canvases_insert_policy" ON user_canvases;
110+
CREATE POLICY "user_canvases_insert_policy" ON user_canvases
111+
FOR INSERT
112+
TO authenticated
113+
WITH CHECK (
114+
auth.uid() = user_canvases.user_id
115+
);
116+
117+
DROP POLICY IF EXISTS "user_canvases_select_policy" ON user_canvases;
118+
CREATE POLICY "user_canvases_select_policy" ON user_canvases
119+
FOR SELECT
120+
TO authenticated
121+
USING (
122+
auth.uid() = user_canvases.user_id
123+
);
124+
125+
DROP POLICY IF EXISTS "user_canvases_update_policy" ON user_canvases;
126+
CREATE POLICY "user_canvases_update_policy" ON user_canvases
127+
FOR UPDATE
128+
TO authenticated
129+
USING (auth.uid() = user_canvases.user_id)
130+
WITH CHECK (auth.uid() = user_canvases.user_id);
131+
132+
DROP POLICY IF EXISTS "user_canvases_delete_policy" ON user_canvases;
133+
CREATE POLICY "user_canvases_delete_policy" ON user_canvases
134+
FOR DELETE
135+
TO authenticated
136+
USING (auth.uid() = user_canvases.user_id);
137+
138+
-- FRAMES POLICIES
139+
DROP POLICY IF EXISTS "frames_insert_policy" ON frames;
140+
CREATE POLICY "frames_insert_policy" ON frames
141+
FOR INSERT
142+
TO authenticated
143+
WITH CHECK (user_has_canvas_access(frames.canvas_id, ARRAY['owner', 'admin']));
144+
145+
DROP POLICY IF EXISTS "frames_select_policy" ON frames;
146+
CREATE POLICY "frames_select_policy" ON frames
147+
FOR SELECT
148+
TO authenticated
149+
USING (user_has_canvas_access(frames.canvas_id, ARRAY['owner', 'admin']));
150+
151+
DROP POLICY IF EXISTS "frames_update_policy" ON frames;
152+
CREATE POLICY "frames_update_policy" ON frames
153+
FOR UPDATE
154+
TO authenticated
155+
USING (user_has_canvas_access(frames.canvas_id, ARRAY['owner', 'admin']))
156+
WITH CHECK (user_has_canvas_access(frames.canvas_id, ARRAY['owner', 'admin']));
157+
158+
DROP POLICY IF EXISTS "frames_delete_policy" ON frames;
159+
CREATE POLICY "frames_delete_policy" ON frames
160+
FOR DELETE
161+
TO authenticated
162+
USING (user_has_canvas_access(frames.canvas_id, ARRAY['owner', 'admin']));
163+
164+
-- USERS POLICIES
165+
DROP POLICY IF EXISTS "users_insert_policy" ON users;
166+
CREATE POLICY "users_insert_policy" ON users
167+
FOR INSERT
168+
TO authenticated
169+
WITH CHECK (auth.uid() = users.id);
170+
171+
DROP POLICY IF EXISTS "users_select_policy" ON users;
172+
CREATE POLICY "users_select_policy" ON users
173+
FOR SELECT
174+
TO authenticated
175+
USING (auth.uid() = users.id);
176+
177+
DROP POLICY IF EXISTS "users_update_policy" ON users;
178+
CREATE POLICY "users_update_policy" ON users
179+
FOR UPDATE
180+
TO authenticated
181+
USING (auth.uid() = users.id)
182+
WITH CHECK (auth.uid() = users.id);
183+
184+
DROP POLICY IF EXISTS "users_delete_policy" ON users;
185+
CREATE POLICY "users_delete_policy" ON users
186+
FOR DELETE
187+
TO authenticated
188+
USING (auth.uid() = users.id);
189+
190+
-- USER_SETTINGS POLICIES
191+
DROP POLICY IF EXISTS "user_settings_insert_policy" ON user_settings;
192+
CREATE POLICY "user_settings_insert_policy" ON user_settings
193+
FOR INSERT
194+
TO authenticated
195+
WITH CHECK (auth.uid() = user_settings.user_id);
196+
197+
DROP POLICY IF EXISTS "user_settings_select_policy" ON user_settings;
198+
CREATE POLICY "user_settings_select_policy" ON user_settings
199+
FOR SELECT
200+
TO authenticated
201+
USING (auth.uid() = user_settings.user_id);
202+
203+
DROP POLICY IF EXISTS "user_settings_update_policy" ON user_settings;
204+
CREATE POLICY "user_settings_update_policy" ON user_settings
205+
FOR UPDATE
206+
TO authenticated
207+
USING (auth.uid() = user_settings.user_id)
208+
WITH CHECK (auth.uid() = user_settings.user_id);
209+
210+
DROP POLICY IF EXISTS "user_settings_delete_policy" ON user_settings;
211+
CREATE POLICY "user_settings_delete_policy" ON user_settings
212+
FOR DELETE
213+
TO authenticated
214+
USING (auth.uid() = user_settings.user_id);
215+
216+
-- PROJECT_INVITATIONS POLICIES
217+
DROP POLICY IF EXISTS "project_invitations_insert_policy" ON project_invitations;
218+
CREATE POLICY "project_invitations_insert_policy" ON project_invitations
219+
FOR INSERT
220+
TO authenticated
221+
WITH CHECK (
222+
auth.uid() = project_invitations.inviter_id AND
223+
user_has_project_access(project_invitations.project_id, ARRAY['owner', 'admin'])
224+
);
225+
226+
DROP POLICY IF EXISTS "project_invitations_select_policy" ON project_invitations;
227+
CREATE POLICY "project_invitations_select_policy" ON project_invitations
228+
FOR SELECT
229+
TO authenticated
230+
USING (
231+
user_has_project_access(project_invitations.project_id, ARRAY['owner', 'admin']) OR
232+
EXISTS (
233+
SELECT 1 FROM auth.users
234+
WHERE auth.users.id = auth.uid()
235+
AND auth.users.email = project_invitations.invitee_email
236+
)
237+
);
238+
239+
DROP POLICY IF EXISTS "project_invitations_update_policy" ON project_invitations;
240+
CREATE POLICY "project_invitations_update_policy" ON project_invitations
241+
FOR UPDATE
242+
TO authenticated
243+
USING (
244+
user_has_project_access(project_invitations.project_id, ARRAY['owner', 'admin']) OR
245+
EXISTS (
246+
SELECT 1 FROM auth.users
247+
WHERE auth.users.id = auth.uid()
248+
AND auth.users.email = project_invitations.invitee_email
249+
)
250+
)
251+
WITH CHECK (
252+
user_has_project_access(project_invitations.project_id, ARRAY['owner', 'admin']) OR
253+
EXISTS (
254+
SELECT 1 FROM auth.users
255+
WHERE auth.users.id = auth.uid()
256+
AND auth.users.email = project_invitations.invitee_email
257+
)
258+
);
259+
260+
DROP POLICY IF EXISTS "project_invitations_delete_policy" ON project_invitations;
261+
CREATE POLICY "project_invitations_delete_policy" ON project_invitations
262+
FOR DELETE
263+
TO authenticated
264+
USING (
265+
auth.uid() = project_invitations.inviter_id OR
266+
user_has_project_access(project_invitations.project_id, ARRAY['owner', 'admin']) OR
267+
EXISTS (
268+
SELECT 1 FROM auth.users
269+
WHERE auth.users.id = auth.uid()
270+
AND auth.users.email = project_invitations.invitee_email
271+
)
272+
);
273+

0 commit comments

Comments
 (0)