Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions packages/database/src/dbTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,24 @@ export type Database = {
},
]
}
group_membership: {
Row: {
admin: boolean | null
group_id: string
member_id: string
}
Insert: {
admin?: boolean | null
group_id: string
member_id: string
}
Update: {
admin?: boolean | null
group_id?: string
member_id?: string
}
Relationships: []
}
LocalAccess: {
Row: {
account_id: number
Expand Down Expand Up @@ -612,18 +630,6 @@ export type Database = {
}
Relationships: []
}
result: {
Row: {
max: string | null
}
Insert: {
max?: string | null
}
Update: {
max?: string | null
}
Relationships: []
}
Space: {
Row: {
id: number
Expand Down Expand Up @@ -684,7 +690,7 @@ export type Database = {
id: number
last_success_start: string | null
last_task_end: string | null
last_task_start: string | null
last_task_start: string
status: Database["public"]["Enums"]["task_status"] | null
sync_function: string | null
sync_target: number | null
Expand All @@ -697,7 +703,7 @@ export type Database = {
id?: number
last_success_start?: string | null
last_task_end?: string | null
last_task_start?: string | null
last_task_start: string
status?: Database["public"]["Enums"]["task_status"] | null
sync_function?: string | null
sync_target?: number | null
Expand All @@ -710,7 +716,7 @@ export type Database = {
id?: number
last_success_start?: string | null
last_task_end?: string | null
last_task_start?: string | null
last_task_start?: string
status?: Database["public"]["Enums"]["task_status"] | null
sync_function?: string | null
sync_target?: number | null
Expand Down Expand Up @@ -1452,6 +1458,8 @@ export type Database = {
}
Returns: string
}
group_exists: { Args: { group_id_: string }; Returns: boolean }
in_group: { Args: { group_id_: string }; Returns: boolean }
in_space: { Args: { space_id: number }; Returns: boolean }
instances_of_schema:
| {
Expand All @@ -1478,6 +1486,7 @@ export type Database = {
isSetofReturn: true
}
}
is_group_admin: { Args: { group_id_: string }; Returns: boolean }
is_my_account: { Args: { account_id: number }; Returns: boolean }
match_content_embeddings: {
Args: {
Expand All @@ -1503,6 +1512,7 @@ export type Database = {
}[]
}
my_space_ids: { Args: never; Returns: number[] }
my_user_accounts: { Args: never; Returns: string[] }
propose_sync_task: {
Args: {
s_function: string
Expand Down
142 changes: 142 additions & 0 deletions packages/database/supabase/migrations/20260101183250_group_access.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
CREATE TABLE public.group_membership (
member_id UUID NOT NULL,
group_id UUID NOT NULL,
admin BOOLEAN DEFAULT true
);

ALTER TABLE public.group_membership
ADD CONSTRAINT group_membership_pkey PRIMARY KEY (member_id, group_id);

CREATE INDEX IF NOT EXISTS group_membership_group_idx ON public.group_membership (group_id);

ALTER TABLE public.group_membership OWNER TO "postgres";

COMMENT ON TABLE public.group_membership IS 'A group membership table';
COMMENT ON COLUMN public.group_membership.member_id IS 'The member of the group';
COMMENT ON COLUMN public.group_membership.group_id IS 'The group identifier';

ALTER TABLE ONLY public.group_membership
ADD CONSTRAINT "group_membership_member_id_fkey" FOREIGN KEY (
member_id
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;

ALTER TABLE ONLY public.group_membership
ADD CONSTRAINT "group_membership_group_id_fkey" FOREIGN KEY (
group_id
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;


GRANT ALL ON TABLE public.group_membership TO authenticated;
GRANT ALL ON TABLE public.group_membership TO service_role;
REVOKE ALL ON TABLE public.group_membership FROM anon;

CREATE OR REPLACE FUNCTION public.in_group(group_id_ UUID) RETURNS BOOLEAN
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT true FROM public.group_membership
WHERE member_id = auth.uid() AND group_id = group_id_);
$$;

CREATE OR REPLACE FUNCTION public.is_group_admin(group_id_ UUID) RETURNS BOOLEAN
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT true FROM public.group_membership
WHERE member_id = auth.uid() AND group_id = group_id_ AND admin);
$$;

CREATE OR REPLACE FUNCTION public.group_exists(group_id_ UUID) RETURNS BOOLEAN
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT true FROM public.group_membership WHERE group_id = group_id_ LIMIT 1);
$$;

ALTER TABLE public.group_membership ENABLE ROW LEVEL SECURITY;
CREATE POLICY group_membership_select_policy ON public.group_membership FOR SELECT USING (public.in_group(group_id));
CREATE POLICY group_membership_delete_policy ON public.group_membership FOR DELETE USING (member_id = auth.uid() OR public.is_group_admin(group_id));
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));
CREATE POLICY group_membership_update_policy ON public.group_membership FOR UPDATE WITH CHECK (public.is_group_admin(group_id));


CREATE OR REPLACE FUNCTION public.my_user_accounts() RETURNS SETOF UUID
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT auth.uid() WHERE auth.uid() IS NOT NULL UNION
SELECT group_id FROM public.group_membership
WHERE member_id = auth.uid();
$$;

COMMENT ON FUNCTION public.my_user_accounts IS 'security utility: The uids which give me access, either as myself or as a group member.';

CREATE OR REPLACE FUNCTION public.my_space_ids() RETURNS BIGINT []
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT COALESCE(array_agg(distinct space_id), '{}') AS ids
FROM public."SpaceAccess"
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts);
$$;

CREATE OR REPLACE FUNCTION public.in_space(space_id BIGINT) RETURNS boolean
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT 1 FROM public."SpaceAccess" AS sa
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
WHERE sa.space_id = in_space.space_id);
$$;

CREATE OR REPLACE FUNCTION public.account_in_shared_space(p_account_id BIGINT) RETURNS boolean
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql AS $$
SELECT EXISTS (
SELECT 1
FROM public."LocalAccess" AS la
JOIN public."SpaceAccess" AS sa USING (space_id)
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
WHERE la.account_id = p_account_id
);
$$;

CREATE OR REPLACE FUNCTION public.unowned_account_in_shared_space(p_account_id BIGINT) RETURNS boolean
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql AS $$
SELECT EXISTS (
SELECT 1
FROM public."SpaceAccess" AS sa
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
JOIN public."LocalAccess" AS la USING (space_id)
JOIN public."PlatformAccount" AS pa ON (pa.id=la.account_id)
WHERE la.account_id = p_account_id
AND pa.dg_account IS NULL
);
$$;

CREATE OR REPLACE VIEW public.my_accounts AS
SELECT
id,
name,
platform,
account_local_id,
write_permission,
active,
agent_type,
metadata,
dg_account
FROM public."PlatformAccount"
WHERE id IN (
SELECT "LocalAccess".account_id FROM public."LocalAccess"
JOIN public."SpaceAccess" USING (space_id)
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts)
);
95 changes: 89 additions & 6 deletions packages/database/supabase/schemas/account.sql
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,37 @@ GRANT ALL ON TABLE public."SpaceAccess" TO authenticated;
GRANT ALL ON TABLE public."SpaceAccess" TO service_role;
REVOKE ALL ON TABLE public."SpaceAccess" FROM anon;

CREATE TABLE IF NOT EXISTS public.group_membership (
member_id UUID NOT NULL,
group_id UUID NOT NULL,
admin BOOLEAN DEFAULT true
);

ALTER TABLE public.group_membership
ADD CONSTRAINT group_membership_pkey PRIMARY KEY (member_id, group_id);

CREATE INDEX IF NOT EXISTS group_membership_group_idx ON public.group_membership (group_id);

ALTER TABLE public.group_membership OWNER TO "postgres";

COMMENT ON TABLE public.group_membership IS 'A group membership table';
COMMENT ON COLUMN public.group_membership.member_id IS 'The member of the group';
COMMENT ON COLUMN public.group_membership.group_id IS 'The group identifier';

ALTER TABLE ONLY public.group_membership
ADD CONSTRAINT "group_membership_member_id_fkey" FOREIGN KEY (
member_id
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;

ALTER TABLE ONLY public.group_membership
ADD CONSTRAINT "group_membership_group_id_fkey" FOREIGN KEY (
group_id
) REFERENCES auth.users (id) ON UPDATE CASCADE ON DELETE CASCADE;

REVOKE ALL ON TABLE public.group_membership FROM anon;
GRANT ALL ON TABLE public.group_membership TO authenticated;
GRANT ALL ON TABLE public.group_membership TO service_role;

CREATE TYPE public.account_local_input AS (
-- PlatformAccount columns
name VARCHAR,
Expand Down Expand Up @@ -232,14 +263,52 @@ $$;

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

CREATE OR REPLACE FUNCTION public.my_user_accounts() RETURNS SETOF UUID
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT auth.uid() WHERE auth.uid() IS NOT NULL UNION
SELECT group_id FROM public.group_membership
WHERE member_id = auth.uid();
$$;

COMMENT ON FUNCTION public.my_user_accounts IS 'security utility: The uids which give me access, either as myself or as a group member.';

CREATE OR REPLACE FUNCTION public.in_group(group_id_ UUID) RETURNS BOOLEAN
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT true FROM public.group_membership
WHERE member_id = auth.uid() AND group_id = group_id_);
$$;

CREATE OR REPLACE FUNCTION public.is_group_admin(group_id_ UUID) RETURNS BOOLEAN
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT true FROM public.group_membership
WHERE member_id = auth.uid() AND group_id = group_id_ AND admin);
$$;

CREATE OR REPLACE FUNCTION public.group_exists(group_id_ UUID) RETURNS BOOLEAN
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT true FROM public.group_membership WHERE group_id = group_id_ LIMIT 1);
$$;

CREATE OR REPLACE FUNCTION public.my_space_ids() RETURNS BIGINT []
STABLE SECURITY DEFINER
SET search_path = ''
LANGUAGE sql
AS $$
SELECT COALESCE(array_agg(distinct space_id), '{}') AS ids
FROM public."SpaceAccess"
WHERE account_uid = auth.uid();
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts);
$$;
COMMENT ON FUNCTION public.my_space_ids IS 'security utility: all spaces the user has access to';

Expand All @@ -250,8 +319,8 @@ SET search_path = ''
LANGUAGE sql
AS $$
SELECT EXISTS (SELECT 1 FROM public."SpaceAccess" AS sa
WHERE sa.space_id = in_space.space_id
AND sa.account_uid=auth.uid());
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
WHERE sa.space_id = in_space.space_id);
$$;

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

Expand All @@ -279,10 +348,10 @@ LANGUAGE sql AS $$
SELECT EXISTS (
SELECT 1
FROM public."SpaceAccess" AS sa
JOIN public.my_user_accounts() ON (sa.account_uid = my_user_accounts)
JOIN public."LocalAccess" AS la USING (space_id)
JOIN public."PlatformAccount" AS pa ON (pa.id=la.account_id)
WHERE la.account_id = p_account_id
AND sa.account_uid = auth.uid()
AND pa.dg_account IS NULL
);
$$;
Expand Down Expand Up @@ -328,7 +397,7 @@ FROM public."PlatformAccount"
WHERE id IN (
SELECT "LocalAccess".account_id FROM public."LocalAccess"
JOIN public."SpaceAccess" USING (space_id)
WHERE "SpaceAccess".account_uid = auth.uid()
JOIN public.my_user_accounts() ON (account_uid = my_user_accounts)
);

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

DROP POLICY IF EXISTS agent_identifier_update_policy ON public."AgentIdentifier";
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));

ALTER TABLE public.group_membership ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS group_membership_select_policy ON public.group_membership;
CREATE POLICY group_membership_select_policy ON public.group_membership FOR SELECT USING (public.in_group(group_id));

DROP POLICY IF EXISTS group_membership_delete_policy ON public.group_membership;
CREATE POLICY group_membership_delete_policy ON public.group_membership FOR DELETE USING (member_id = auth.uid() OR public.is_group_admin(group_id));

DROP POLICY IF EXISTS group_membership_insert_policy ON public.group_membership;
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));

DROP POLICY IF EXISTS group_membership_update_policy ON public.group_membership;
CREATE POLICY group_membership_update_policy ON public.group_membership FOR UPDATE WITH CHECK (public.is_group_admin(group_id));