managarten/chat/apps/mobile/scripts/spaces/create_spaces_rls.sql
Till-JS c638a7ffee feat(chat): integrate chat project into monorepo with full app structure
- Restructure chat as apps/mobile, apps/web, apps/landing, backend
- Add NestJS backend for secure Azure OpenAI API calls
- Remove exposed API key from mobile app (security fix)
- Add shared chat-types package
- Create SvelteKit web app scaffold
- Create Astro landing page scaffold
- Update pnpm workspace configuration
- Add project-level CLAUDE.md documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 13:48:24 +01:00

176 lines
No EOL
4.4 KiB
PL/PgSQL

-- Enable Row Level Security for spaces tables
ALTER TABLE public.spaces ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.space_members ENABLE ROW LEVEL SECURITY;
-- RLS policies for spaces
-- Space owners can do everything with their spaces
CREATE POLICY spaces_owner_policy
ON public.spaces
TO authenticated
USING (owner_id = auth.uid());
-- Members can view spaces they belong to
CREATE POLICY spaces_member_select_policy
ON public.spaces
FOR SELECT
TO authenticated
USING (
id IN (
SELECT space_id
FROM public.space_members
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
)
);
-- RLS policies for space_members
-- Space owners can manage all members
CREATE POLICY space_members_owner_policy
ON public.space_members
TO authenticated
USING (
space_id IN (
SELECT id FROM public.spaces WHERE owner_id = auth.uid()
)
);
-- Space admins can manage members (except owners)
CREATE POLICY space_members_admin_policy
ON public.space_members
TO authenticated
USING (
space_id IN (
SELECT space_id FROM public.space_members
WHERE user_id = auth.uid() AND role = 'admin' AND invitation_status = 'accepted'
)
AND role != 'owner'
);
-- Users can see which spaces they are members of
CREATE POLICY space_members_self_select_policy
ON public.space_members
FOR SELECT
TO authenticated
USING (user_id = auth.uid());
-- Users can accept/decline their own invitations
CREATE POLICY space_members_invitation_update_policy
ON public.space_members
FOR UPDATE
TO authenticated
USING (user_id = auth.uid())
WITH CHECK (
user_id = auth.uid()
AND (OLD.invitation_status = 'pending')
AND (NEW.invitation_status IN ('accepted', 'declined'))
AND (OLD.role = NEW.role)
AND (OLD.space_id = NEW.space_id)
AND (OLD.user_id = NEW.user_id)
);
-- Update RLS policies for conversations
-- Modify existing policies to include space-based access
DROP POLICY IF EXISTS conversations_select_policy ON conversations;
CREATE POLICY conversations_select_policy
ON conversations
FOR SELECT
TO authenticated
USING (
user_id = auth.uid()
OR
(
space_id IN (
SELECT space_id FROM public.space_members
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
)
)
);
-- Allow space members to create conversations in spaces they belong to
CREATE POLICY conversations_space_insert_policy
ON conversations
FOR INSERT
TO authenticated
WITH CHECK (
user_id = auth.uid()
AND
(
space_id IS NULL
OR
space_id IN (
SELECT space_id FROM public.space_members
WHERE user_id = auth.uid() AND invitation_status = 'accepted'
)
)
);
-- Allow updates to conversations in spaces based on role
DROP POLICY IF EXISTS conversations_update_policy ON conversations;
CREATE POLICY conversations_update_policy
ON conversations
FOR UPDATE
TO authenticated
USING (
user_id = auth.uid()
OR
(
space_id IN (
SELECT sm.space_id FROM public.space_members sm
WHERE sm.user_id = auth.uid()
AND sm.invitation_status = 'accepted'
AND sm.role IN ('owner', 'admin')
)
)
);
-- Allow deletion of conversations in spaces based on role
DROP POLICY IF EXISTS conversations_delete_policy ON conversations;
CREATE POLICY conversations_delete_policy
ON conversations
FOR DELETE
TO authenticated
USING (
user_id = auth.uid()
OR
(
space_id IN (
SELECT sm.space_id FROM public.space_members sm
WHERE sm.user_id = auth.uid()
AND sm.invitation_status = 'accepted'
AND sm.role IN ('owner', 'admin')
)
)
);
-- Helper function to check if a user has access to a space
CREATE OR REPLACE FUNCTION public.user_has_space_access(space_uuid UUID, role_level TEXT DEFAULT 'viewer')
RETURNS BOOLEAN AS $$
DECLARE
has_access BOOLEAN;
role_hierarchy TEXT[];
BEGIN
-- Define role hierarchy from highest to lowest
role_hierarchy := ARRAY['owner', 'admin', 'member', 'viewer'];
-- Find position of requested role in hierarchy
WITH role_positions AS (
SELECT
unnest(role_hierarchy) AS role,
row_number() OVER () AS position
)
SELECT EXISTS (
SELECT 1 FROM public.space_members sm
JOIN role_positions rp1 ON sm.role = rp1.role
JOIN role_positions rp2 ON rp2.role = role_level
WHERE sm.space_id = space_uuid
AND sm.user_id = auth.uid()
AND sm.invitation_status = 'accepted'
AND rp1.position <= rp2.position -- Check if user's role is at least the required level
) INTO has_access;
RETURN has_access;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;