Skip to content

Commit 6044d76

Browse files
authored
eng-1232 create group table
1 parent 950bb32 commit 6044d76

File tree

3 files changed

+256
-21
lines changed

3 files changed

+256
-21
lines changed

packages/database/src/dbTypes.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,24 @@ export type Database = {
532532
},
533533
]
534534
}
535+
group_membership: {
536+
Row: {
537+
admin: boolean | null
538+
group_id: string
539+
member_id: string
540+
}
541+
Insert: {
542+
admin?: boolean | null
543+
group_id: string
544+
member_id: string
545+
}
546+
Update: {
547+
admin?: boolean | null
548+
group_id?: string
549+
member_id?: string
550+
}
551+
Relationships: []
552+
}
535553
LocalAccess: {
536554
Row: {
537555
account_id: number
@@ -612,18 +630,6 @@ export type Database = {
612630
}
613631
Relationships: []
614632
}
615-
result: {
616-
Row: {
617-
max: string | null
618-
}
619-
Insert: {
620-
max?: string | null
621-
}
622-
Update: {
623-
max?: string | null
624-
}
625-
Relationships: []
626-
}
627633
Space: {
628634
Row: {
629635
id: number
@@ -684,7 +690,7 @@ export type Database = {
684690
id: number
685691
last_success_start: string | null
686692
last_task_end: string | null
687-
last_task_start: string | null
693+
last_task_start: string
688694
status: Database["public"]["Enums"]["task_status"] | null
689695
sync_function: string | null
690696
sync_target: number | null
@@ -697,7 +703,7 @@ export type Database = {
697703
id?: number
698704
last_success_start?: string | null
699705
last_task_end?: string | null
700-
last_task_start?: string | null
706+
last_task_start: string
701707
status?: Database["public"]["Enums"]["task_status"] | null
702708
sync_function?: string | null
703709
sync_target?: number | null
@@ -710,7 +716,7 @@ export type Database = {
710716
id?: number
711717
last_success_start?: string | null
712718
last_task_end?: string | null
713-
last_task_start?: string | null
719+
last_task_start?: string
714720
status?: Database["public"]["Enums"]["task_status"] | null
715721
sync_function?: string | null
716722
sync_target?: number | null
@@ -1452,6 +1458,8 @@ export type Database = {
14521458
}
14531459
Returns: string
14541460
}
1461+
group_exists: { Args: { group_id_: string }; Returns: boolean }
1462+
in_group: { Args: { group_id_: string }; Returns: boolean }
14551463
in_space: { Args: { space_id: number }; Returns: boolean }
14561464
instances_of_schema:
14571465
| {
@@ -1478,6 +1486,7 @@ export type Database = {
14781486
isSetofReturn: true
14791487
}
14801488
}
1489+
is_group_admin: { Args: { group_id_: string }; Returns: boolean }
14811490
is_my_account: { Args: { account_id: number }; Returns: boolean }
14821491
match_content_embeddings: {
14831492
Args: {
@@ -1503,6 +1512,7 @@ export type Database = {
15031512
}[]
15041513
}
15051514
my_space_ids: { Args: never; Returns: number[] }
1515+
my_user_accounts: { Args: never; Returns: string[] }
15061516
propose_sync_task: {
15071517
Args: {
15081518
s_function: string
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
CREATE TABLE public.group_membership (
2+
member_id UUID NOT NULL,
3+
group_id UUID NOT NULL,
4+
admin BOOLEAN DEFAULT true
5+
);
6+
7+
ALTER TABLE public.group_membership
8+
ADD CONSTRAINT group_membership_pkey PRIMARY KEY (member_id, group_id);
9+
10+
CREATE INDEX IF NOT EXISTS group_membership_group_idx ON public.group_membership (group_id);
11+
12+
ALTER TABLE public.group_membership OWNER TO "postgres";
13+
14+
COMMENT ON TABLE public.group_membership IS 'A group membership table';
15+
COMMENT ON COLUMN public.group_membership.member_id IS 'The member of the group';
16+
COMMENT ON COLUMN public.group_membership.group_id IS 'The group identifier';
17+
18+
ALTER TABLE ONLY public.group_membership
19+
ADD CONSTRAINT "group_membership_member_id_fkey" FOREIGN KEY (
20+
member_id
21+
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;
22+
23+
ALTER TABLE ONLY public.group_membership
24+
ADD CONSTRAINT "group_membership_group_id_fkey" FOREIGN KEY (
25+
group_id
26+
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;
27+
28+
29+
GRANT ALL ON TABLE public.group_membership TO authenticated;
30+
GRANT ALL ON TABLE public.group_membership TO service_role;
31+
REVOKE ALL ON TABLE public.group_membership FROM anon;
32+
33+
CREATE OR REPLACE FUNCTION public.in_group(group_id_ UUID) RETURNS BOOLEAN
34+
STABLE SECURITY DEFINER
35+
SET search_path = ''
36+
LANGUAGE sql
37+
AS $$
38+
SELECT EXISTS (SELECT true FROM public.group_membership
39+
WHERE member_id = auth.uid() AND group_id = group_id_);
40+
$$;
41+
42+
CREATE OR REPLACE FUNCTION public.is_group_admin(group_id_ UUID) RETURNS BOOLEAN
43+
STABLE SECURITY DEFINER
44+
SET search_path = ''
45+
LANGUAGE sql
46+
AS $$
47+
SELECT EXISTS (SELECT true FROM public.group_membership
48+
WHERE member_id = auth.uid() AND group_id = group_id_ AND admin);
49+
$$;
50+
51+
CREATE OR REPLACE FUNCTION public.group_exists(group_id_ UUID) RETURNS BOOLEAN
52+
STABLE SECURITY DEFINER
53+
SET search_path = ''
54+
LANGUAGE sql
55+
AS $$
56+
SELECT EXISTS (SELECT true FROM public.group_membership WHERE group_id = group_id_ LIMIT 1);
57+
$$;
58+
59+
ALTER TABLE public.group_membership ENABLE ROW LEVEL SECURITY;
60+
CREATE POLICY group_membership_select_policy ON public.group_membership FOR SELECT USING (public.in_group(group_id));
61+
CREATE POLICY group_membership_delete_policy ON public.group_membership FOR DELETE USING (member_id = auth.uid() OR public.is_group_admin(group_id));
62+
CREATE POLICY group_membership_insert_policy ON public.group_membership FOR INSERT WITH CHECK (public.is_group_admin(group_id) OR NOT public.group_exists(group_id));
63+
CREATE POLICY group_membership_update_policy ON public.group_membership FOR UPDATE WITH CHECK (public.is_group_admin(group_id));
64+
65+
66+
CREATE OR REPLACE FUNCTION public.my_user_accounts() RETURNS SETOF UUID
67+
STABLE SECURITY DEFINER
68+
SET search_path = ''
69+
LANGUAGE sql
70+
AS $$
71+
SELECT auth.uid() WHERE auth.uid() IS NOT NULL UNION
72+
SELECT group_id FROM public.group_membership
73+
WHERE member_id = auth.uid();
74+
$$;
75+
76+
COMMENT ON FUNCTION public.my_user_accounts IS 'security utility: The uids which give me access, either as myself or as a group member.';
77+
78+
CREATE OR REPLACE FUNCTION public.my_space_ids() RETURNS BIGINT []
79+
STABLE SECURITY DEFINER
80+
SET search_path = ''
81+
LANGUAGE sql
82+
AS $$
83+
SELECT COALESCE(array_agg(distinct space_id), '{}') AS ids
84+
FROM public."SpaceAccess"
85+
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts);
86+
$$;
87+
88+
CREATE OR REPLACE FUNCTION public.in_space(space_id BIGINT) RETURNS boolean
89+
STABLE SECURITY DEFINER
90+
SET search_path = ''
91+
LANGUAGE sql
92+
AS $$
93+
SELECT EXISTS (SELECT 1 FROM public."SpaceAccess" AS sa
94+
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
95+
WHERE sa.space_id = in_space.space_id);
96+
$$;
97+
98+
CREATE OR REPLACE FUNCTION public.account_in_shared_space(p_account_id BIGINT) RETURNS boolean
99+
STABLE SECURITY DEFINER
100+
SET search_path = ''
101+
LANGUAGE sql AS $$
102+
SELECT EXISTS (
103+
SELECT 1
104+
FROM public."LocalAccess" AS la
105+
JOIN public."SpaceAccess" AS sa USING (space_id)
106+
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
107+
WHERE la.account_id = p_account_id
108+
);
109+
$$;
110+
111+
CREATE OR REPLACE FUNCTION public.unowned_account_in_shared_space(p_account_id BIGINT) RETURNS boolean
112+
STABLE SECURITY DEFINER
113+
SET search_path = ''
114+
LANGUAGE sql AS $$
115+
SELECT EXISTS (
116+
SELECT 1
117+
FROM public."SpaceAccess" AS sa
118+
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
119+
JOIN public."LocalAccess" AS la USING (space_id)
120+
JOIN public."PlatformAccount" AS pa ON (pa.id=la.account_id)
121+
WHERE la.account_id = p_account_id
122+
AND pa.dg_account IS NULL
123+
);
124+
$$;
125+
126+
CREATE OR REPLACE VIEW public.my_accounts AS
127+
SELECT
128+
id,
129+
name,
130+
platform,
131+
account_local_id,
132+
write_permission,
133+
active,
134+
agent_type,
135+
metadata,
136+
dg_account
137+
FROM public."PlatformAccount"
138+
WHERE id IN (
139+
SELECT "LocalAccess".account_id FROM public."LocalAccess"
140+
JOIN public."SpaceAccess" USING (space_id)
141+
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts)
142+
);

packages/database/supabase/schemas/account.sql

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,37 @@ GRANT ALL ON TABLE public."SpaceAccess" TO authenticated;
128128
GRANT ALL ON TABLE public."SpaceAccess" TO service_role;
129129
REVOKE ALL ON TABLE public."SpaceAccess" FROM anon;
130130

131+
CREATE TABLE IF NOT EXISTS public.group_membership (
132+
member_id UUID NOT NULL,
133+
group_id UUID NOT NULL,
134+
admin BOOLEAN DEFAULT true
135+
);
136+
137+
ALTER TABLE public.group_membership
138+
ADD CONSTRAINT group_membership_pkey PRIMARY KEY (member_id, group_id);
139+
140+
CREATE INDEX IF NOT EXISTS group_membership_group_idx ON public.group_membership (group_id);
141+
142+
ALTER TABLE public.group_membership OWNER TO "postgres";
143+
144+
COMMENT ON TABLE public.group_membership IS 'A group membership table';
145+
COMMENT ON COLUMN public.group_membership.member_id IS 'The member of the group';
146+
COMMENT ON COLUMN public.group_membership.group_id IS 'The group identifier';
147+
148+
ALTER TABLE ONLY public.group_membership
149+
ADD CONSTRAINT "group_membership_member_id_fkey" FOREIGN KEY (
150+
member_id
151+
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;
152+
153+
ALTER TABLE ONLY public.group_membership
154+
ADD CONSTRAINT "group_membership_group_id_fkey" FOREIGN KEY (
155+
group_id
156+
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;
157+
158+
REVOKE ALL ON TABLE public.group_membership FROM anon;
159+
GRANT ALL ON TABLE public.group_membership TO authenticated;
160+
GRANT ALL ON TABLE public.group_membership TO service_role;
161+
131162
CREATE TYPE public.account_local_input AS (
132163
-- PlatformAccount columns
133164
name VARCHAR,
@@ -232,14 +263,52 @@ $$;
232263

233264
COMMENT ON FUNCTION public.is_my_account IS 'security utility: is this my own account?';
234265

266+
CREATE OR REPLACE FUNCTION public.my_user_accounts() RETURNS SETOF UUID
267+
STABLE SECURITY DEFINER
268+
SET search_path = ''
269+
LANGUAGE sql
270+
AS $$
271+
SELECT auth.uid() WHERE auth.uid() IS NOT NULL UNION
272+
SELECT group_id FROM public.group_membership
273+
WHERE member_id = auth.uid();
274+
$$;
275+
276+
COMMENT ON FUNCTION public.my_user_accounts IS 'security utility: The uids which give me access, either as myself or as a group member.';
277+
278+
CREATE OR REPLACE FUNCTION public.in_group(group_id_ UUID) RETURNS BOOLEAN
279+
STABLE SECURITY DEFINER
280+
SET search_path = ''
281+
LANGUAGE sql
282+
AS $$
283+
SELECT EXISTS (SELECT true FROM public.group_membership
284+
WHERE member_id = auth.uid() AND group_id = group_id_);
285+
$$;
286+
287+
CREATE OR REPLACE FUNCTION public.is_group_admin(group_id_ UUID) RETURNS BOOLEAN
288+
STABLE SECURITY DEFINER
289+
SET search_path = ''
290+
LANGUAGE sql
291+
AS $$
292+
SELECT EXISTS (SELECT true FROM public.group_membership
293+
WHERE member_id = auth.uid() AND group_id = group_id_ AND admin);
294+
$$;
295+
296+
CREATE OR REPLACE FUNCTION public.group_exists(group_id_ UUID) RETURNS BOOLEAN
297+
STABLE SECURITY DEFINER
298+
SET search_path = ''
299+
LANGUAGE sql
300+
AS $$
301+
SELECT EXISTS (SELECT true FROM public.group_membership WHERE group_id = group_id_ LIMIT 1);
302+
$$;
303+
235304
CREATE OR REPLACE FUNCTION public.my_space_ids() RETURNS BIGINT []
236305
STABLE SECURITY DEFINER
237306
SET search_path = ''
238307
LANGUAGE sql
239308
AS $$
240309
SELECT COALESCE(array_agg(distinct space_id), '{}') AS ids
241310
FROM public."SpaceAccess"
242-
WHERE account_uid = auth.uid();
311+
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts);
243312
$$;
244313
COMMENT ON FUNCTION public.my_space_ids IS 'security utility: all spaces the user has access to';
245314

@@ -250,8 +319,8 @@ SET search_path = ''
250319
LANGUAGE sql
251320
AS $$
252321
SELECT EXISTS (SELECT 1 FROM public."SpaceAccess" AS sa
253-
WHERE sa.space_id = in_space.space_id
254-
AND sa.account_uid=auth.uid());
322+
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
323+
WHERE sa.space_id = in_space.space_id);
255324
$$;
256325

257326
COMMENT ON FUNCTION public.in_space IS 'security utility: does current user have access to this space?';
@@ -265,8 +334,8 @@ LANGUAGE sql AS $$
265334
SELECT 1
266335
FROM public."LocalAccess" AS la
267336
JOIN public."SpaceAccess" AS sa USING (space_id)
337+
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
268338
WHERE la.account_id = p_account_id
269-
AND sa.account_uid = auth.uid()
270339
);
271340
$$;
272341

@@ -279,10 +348,10 @@ LANGUAGE sql AS $$
279348
SELECT EXISTS (
280349
SELECT 1
281350
FROM public."SpaceAccess" AS sa
351+
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
282352
JOIN public."LocalAccess" AS la USING (space_id)
283353
JOIN public."PlatformAccount" AS pa ON (pa.id=la.account_id)
284354
WHERE la.account_id = p_account_id
285-
AND sa.account_uid = auth.uid()
286355
AND pa.dg_account IS NULL
287356
);
288357
$$;
@@ -328,7 +397,7 @@ FROM public."PlatformAccount"
328397
WHERE id IN (
329398
SELECT "LocalAccess".account_id FROM public."LocalAccess"
330399
JOIN public."SpaceAccess" USING (space_id)
331-
WHERE "SpaceAccess".account_uid = auth.uid()
400+
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts)
332401
);
333402

334403
DROP POLICY IF EXISTS platform_account_policy ON public."PlatformAccount";
@@ -399,3 +468,17 @@ CREATE POLICY agent_identifier_insert_policy ON public."AgentIdentifier" FOR INS
399468

400469
DROP POLICY IF EXISTS agent_identifier_update_policy ON public."AgentIdentifier";
401470
CREATE POLICY agent_identifier_update_policy ON public."AgentIdentifier" FOR UPDATE WITH CHECK (public.unowned_account_in_shared_space(account_id) OR public.is_my_account(account_id));
471+
472+
ALTER TABLE public.group_membership ENABLE ROW LEVEL SECURITY;
473+
474+
DROP POLICY IF EXISTS group_membership_select_policy ON public.group_membership;
475+
CREATE POLICY group_membership_select_policy ON public.group_membership FOR SELECT USING (public.in_group(group_id));
476+
477+
DROP POLICY IF EXISTS group_membership_delete_policy ON public.group_membership;
478+
CREATE POLICY group_membership_delete_policy ON public.group_membership FOR DELETE USING (member_id = auth.uid() OR public.is_group_admin(group_id));
479+
480+
DROP POLICY IF EXISTS group_membership_insert_policy ON public.group_membership;
481+
CREATE POLICY group_membership_insert_policy ON public.group_membership FOR INSERT WITH CHECK (public.is_group_admin(group_id) OR NOT public.group_exists(group_id));
482+
483+
DROP POLICY IF EXISTS group_membership_update_policy ON public.group_membership;
484+
CREATE POLICY group_membership_update_policy ON public.group_membership FOR UPDATE WITH CHECK (public.is_group_admin(group_id));

0 commit comments

Comments
 (0)