feat(sprint-4/etapa-5): modelo de privacidad RLS granular + UI de visibilidad

- SQL 010: tablas user_groups y group_memberships con RLS
- SQL 011: tabla process_access para acceso explícito a procesos
- SQL 012: función is_platform_admin() + políticas granulares (select/insert/update/delete) en processes
- types.ts: Process.ownerId (requerido)
- process-repo: fromRow mapea owner_id→ownerId; toRow preserva ownerId en updates; setShared() no-op documentado
- ImportPage: asigna ownerId: user.id al crear proceso
- LibraryPage: toggle Todos/Míos, OwnershipBadge (🔒/👤) en cada tarjeta, opción "Compartir con equipo" en menú de proceso propio con toast de confirmación
- Tests: ownerId: 'test-user' agregado a todos los fixtures de Process

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 22:37:37 -03:00
parent 025393cd43
commit d426791a53
20 changed files with 281 additions and 28 deletions

View File

@@ -0,0 +1,38 @@
CREATE TABLE IF NOT EXISTS public.user_groups (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
created_by uuid REFERENCES public.users(id),
created_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE public.user_groups ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read_groups" ON public.user_groups
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY "manage_groups" ON public.user_groups
FOR ALL USING (
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND platform_role = 'platform_admin')
OR created_by = auth.uid()
);
CREATE TABLE IF NOT EXISTS public.group_memberships (
user_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
group_id uuid NOT NULL REFERENCES public.user_groups(id) ON DELETE CASCADE,
role text NOT NULL DEFAULT 'member' CHECK (role IN ('owner','member')),
PRIMARY KEY (user_id, group_id)
);
ALTER TABLE public.group_memberships ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read_memberships" ON public.group_memberships
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY "manage_memberships" ON public.group_memberships
FOR ALL USING (
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND platform_role = 'platform_admin')
OR EXISTS (
SELECT 1 FROM public.user_groups
WHERE id = group_memberships.group_id AND created_by = auth.uid()
)
);

View File

@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS public.process_access (
process_id uuid NOT NULL REFERENCES public.processes(id) ON DELETE CASCADE,
grantee_type text NOT NULL CHECK (grantee_type IN ('user','group')),
grantee_id uuid NOT NULL,
permission text NOT NULL DEFAULT 'view' CHECK (permission IN ('view','edit')),
PRIMARY KEY (process_id, grantee_type, grantee_id)
);
ALTER TABLE public.process_access ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read_process_access" ON public.process_access
FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY "manage_process_access" ON public.process_access
FOR ALL USING (
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND platform_role = 'platform_admin')
OR EXISTS (
SELECT 1 FROM public.processes
WHERE id = process_access.process_id AND owner_id = auth.uid()
)
);

View File

@@ -0,0 +1,53 @@
-- Helper: retorna true si el usuario actual es platform_admin
CREATE OR REPLACE FUNCTION public.is_platform_admin()
RETURNS boolean
LANGUAGE sql SECURITY DEFINER
AS $$
SELECT EXISTS (
SELECT 1 FROM public.users
WHERE id = auth.uid() AND platform_role = 'platform_admin'
);
$$;
-- Eliminar política amplia de Sprint 4 Etapa 2
DROP POLICY IF EXISTS "authenticated_all_processes" ON public.processes;
-- Políticas granulares
CREATE POLICY "select_processes" ON public.processes
FOR SELECT USING (
public.is_platform_admin()
OR owner_id = auth.uid()
OR EXISTS (
SELECT 1 FROM public.process_access
WHERE process_id = processes.id
AND (
(grantee_type = 'user' AND grantee_id = auth.uid())
OR (grantee_type = 'group' AND grantee_id IN (
SELECT group_id FROM public.group_memberships WHERE user_id = auth.uid()
))
)
)
);
CREATE POLICY "insert_processes" ON public.processes
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
CREATE POLICY "update_processes" ON public.processes
FOR UPDATE USING (
public.is_platform_admin()
OR owner_id = auth.uid()
OR EXISTS (
SELECT 1 FROM public.process_access
WHERE process_id = processes.id
AND permission = 'edit'
AND (
(grantee_type = 'user' AND grantee_id = auth.uid())
OR (grantee_type = 'group' AND grantee_id IN (
SELECT group_id FROM public.group_memberships WHERE user_id = auth.uid()
))
)
)
);
CREATE POLICY "delete_processes" ON public.processes
FOR DELETE USING (public.is_platform_admin() OR owner_id = auth.uid());