From 3270038dbc41efd11815ba81ca0a489434fa2293 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 18 Jan 2025 21:36:39 +0000 Subject: [PATCH] fix: alter RLS policies --- .../src/views/board/components/Filters.tsx | 66 ++- apps/web/src/views/public/board/CardModal.tsx | 2 +- apps/web/src/views/public/board/index.tsx | 6 +- packages/api/src/routers/card.ts | 5 +- packages/api/src/routers/workspace.ts | 4 +- packages/db/seed.sql | 413 +++++++++++++++++- packages/db/src/repository/board.repo.ts | 22 +- 7 files changed, 457 insertions(+), 61 deletions(-) diff --git a/apps/web/src/views/board/components/Filters.tsx b/apps/web/src/views/board/components/Filters.tsx index 8b714b6a..a8fc0201 100644 --- a/apps/web/src/views/board/components/Filters.tsx +++ b/apps/web/src/views/board/components/Filters.tsx @@ -6,12 +6,54 @@ import { } from "react-icons/hi2"; import { IoFilterOutline } from "react-icons/io5"; -import type { GetBoardByIdOutput } from "@kan/api/types"; - import Button from "~/components/Button"; import CheckboxDropdown from "~/components/CheckboxDropdown"; import { formatToArray } from "~/utils/helpers"; +interface BoardData { + publicId: string; + name: string; + slug: string; + workspace: { + publicId: string; + members?: { + publicId: string; + user: { + name: string | null; + } | null; + }[]; + } | null; + labels: { + publicId: string; + name: string; + colourCode: string | null; + }[]; + lists: { + publicId: string; + name: string; + boardId: number; + index: number; + cards: { + publicId: string; + title: string; + description: string | null; + listId: number; + index: number; + labels: { + publicId: string; + name: string; + colourCode: string | null; + }[]; + members?: { + publicId: string; + user: { + name: string | null; + } | null; + }[]; + }[]; + }[]; +} + const LabelIcon = ({ colourCode }: { colourCode: string | null }) => ( { const router = useRouter(); @@ -58,7 +100,7 @@ const Filters = ({ }; const formattedMembers = - boardData?.workspace?.members.map((member) => ({ + boardData?.workspace?.members?.map((member) => ({ key: member.publicId, value: member.user?.name ?? "", selected: !!router.query.members?.includes(member.publicId), @@ -74,12 +116,16 @@ const Filters = ({ })) ?? []; const groups = [ - { - key: "members", - label: "Members", - icon: , - items: formattedMembers, - }, + ...(formattedMembers.length + ? [ + { + key: "members", + label: "Members", + icon: , + items: formattedMembers, + }, + ] + : []), { key: "labels", label: "Labels", diff --git a/apps/web/src/views/public/board/CardModal.tsx b/apps/web/src/views/public/board/CardModal.tsx index fa130471..89a5bff2 100644 --- a/apps/web/src/views/public/board/CardModal.tsx +++ b/apps/web/src/views/public/board/CardModal.tsx @@ -96,7 +96,7 @@ export function CardModal({ )} -
+

Activity

diff --git a/apps/web/src/views/public/board/index.tsx b/apps/web/src/views/public/board/index.tsx index f50c4020..cd616cd4 100644 --- a/apps/web/src/views/public/board/index.tsx +++ b/apps/web/src/views/public/board/index.tsx @@ -139,7 +139,7 @@ export default function PublicBoardView() { }} >
{card.title}
- {card.labels.length || card.members.length ? ( + {card.labels.length ? (
{card.labels.map((label) => ( {label.name}
))} -
+ {/*
{card.members.map((member) => ( ))} -
+
*/}
) : null} diff --git a/packages/api/src/routers/card.ts b/packages/api/src/routers/card.ts index 2aab920b..daa8f30d 100644 --- a/packages/api/src/routers/card.ts +++ b/packages/api/src/routers/card.ts @@ -8,7 +8,7 @@ import * as labelRepo from "@kan/db/repository/label.repo"; import * as listRepo from "@kan/db/repository/list.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; -import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; export const cardRouter = createTRPCRouter({ create: protectedProcedure @@ -479,7 +479,7 @@ export const cardRouter = createTRPCRouter({ return { newMember: true }; }), - byId: protectedProcedure + byId: publicProcedure .meta({ openapi: { summary: "Get a card by public ID", @@ -487,7 +487,6 @@ export const cardRouter = createTRPCRouter({ path: "/cards/{cardPublicId}", description: "Retrieves a card by its public ID", tags: ["Cards"], - protect: true, }, }) .input(z.object({ cardPublicId: z.string().min(12) })) diff --git a/packages/api/src/routers/workspace.ts b/packages/api/src/routers/workspace.ts index b30ff9a6..3abf14a1 100644 --- a/packages/api/src/routers/workspace.ts +++ b/packages/api/src/routers/workspace.ts @@ -170,7 +170,7 @@ export const workspaceRouter = createTRPCRouter({ ); const reservedOrPremiumWorkspaceSlug = - await workspaceSlugRepo.getWorkspaceSlug(ctx.db, input.slug); + await workspaceSlugRepo.getWorkspaceSlug(ctx.adminDb, input.slug); const isWorkspaceSlugAvailable = await workspaceRepo.isWorkspaceSlugAvailable(ctx.db, input.slug); @@ -258,7 +258,7 @@ export const workspaceRouter = createTRPCRouter({ const slug = input.workspaceSlug.toLowerCase(); // check list of reserved or premium slugs const workspaceSlug = await workspaceSlugRepo.getWorkspaceSlug( - ctx.db, + ctx.adminDb, slug, ); diff --git a/packages/db/seed.sql b/packages/db/seed.sql index 676ff13d..8b20ce30 100644 --- a/packages/db/seed.sql +++ b/packages/db/seed.sql @@ -120,6 +120,8 @@ $$; alter table "_card_labels" enable row level security; alter table "_card_workspace_members" enable row level security; +alter table "_card_activity" enable row level security; +alter table "_card_comments" enable row level security; alter table "board" enable row level security; alter table "card" enable row level security; alter table "import" enable row level security; @@ -128,11 +130,41 @@ alter table "user" enable row level security; alter table "list" enable row level security; alter table "workspace" enable row level security; alter table "workspace_members" enable row level security; +alter table "workspace_slugs" enable row level security; -CREATE POLICY "Allow access to boards in user's workspace" + +/* BOARD */ +CREATE POLICY "Allow access to boards in user's workspace or public boards" ON public.board AS PERMISSIVE -FOR ALL +FOR SELECT +TO anon, authenticated +USING ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + OR visibility = 'public' +); + +CREATE POLICY "Allow inserting boards in user's workspace" +ON public.board +AS PERMISSIVE +FOR INSERT +TO authenticated +WITH CHECK ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) +); + +CREATE POLICY "Allow updating boards in user's workspace" +ON public.board +AS PERMISSIVE +FOR UPDATE TO authenticated USING ( "workspaceId" IN ( @@ -142,10 +174,66 @@ USING ( ) ); -CREATE POLICY "Allow access to lists in user's workspace" +CREATE POLICY "Allow deleting boards in user's workspace" +ON public.board +AS PERMISSIVE +FOR DELETE +TO authenticated +USING ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) +); + +CREATE POLICY "Allow modifications to boards in user's workspace" +ON public.board +AS PERMISSIVE +FOR INSERT +TO authenticated +USING ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) +); + +/* LIST */ +CREATE POLICY "Allow access to lists in user's workspace or public boards" ON public.list AS PERMISSIVE -FOR ALL +FOR SELECT +TO anon, authenticated +USING ( + "boardId" IN ( + SELECT b.id + FROM board b + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) +); + +CREATE POLICY "Allow inserting lists in user's workspace" +ON public.list +AS PERMISSIVE +FOR INSERT +TO authenticated +WITH CHECK ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +CREATE POLICY "Allow updating lists in user's workspace" +ON public.list +AS PERMISSIVE +FOR UPDATE TO authenticated USING ( "boardId" IN ( @@ -156,25 +244,116 @@ USING ( ) ); -CREATE POLICY "Allow access to cards in user's workspace" +CREATE POLICY "Allow deleting lists in user's workspace" +ON public.list +AS PERMISSIVE +FOR DELETE +TO authenticated +USING ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +/* CARD */ +CREATE POLICY "Allow access to cards in user's workspace or public boards" ON public.card AS PERMISSIVE -FOR ALL +FOR SELECT +TO anon, authenticated +USING ( + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) +); + +CREATE POLICY "Allow inserting cards in user's workspace" +ON public.card +AS PERMISSIVE +FOR INSERT +TO authenticated +WITH CHECK ( + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +CREATE POLICY "Allow updating cards in user's workspace" +ON public.card +AS PERMISSIVE +FOR UPDATE TO authenticated USING ( "listId" IN ( SELECT l.id FROM list l - JOIN board b ON l."boardId" = b."id" + JOIN board b ON l."boardId" = b.id JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" WHERE wm."userId" = auth.uid() ) ); -CREATE POLICY "Allow access to labels in user's workspace" +CREATE POLICY "Allow deleting cards in user's workspace" +ON public.card +AS PERMISSIVE +FOR DELETE +TO authenticated +USING ( + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +/* LABEL */ +CREATE POLICY "Allow access to labels in user's workspace or public boards" ON public.label AS PERMISSIVE -FOR ALL +FOR SELECT +TO anon, authenticated +USING ( + "boardId" IN ( + SELECT b.id + FROM board b + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) +); + +CREATE POLICY "Allow inserting labels in user's workspace" +ON public.label +AS PERMISSIVE +FOR INSERT +TO authenticated +WITH CHECK ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +CREATE POLICY "Allow updating labels in user's workspace" +ON public.label +AS PERMISSIVE +FOR UPDATE TO authenticated USING ( "boardId" IN ( @@ -185,10 +364,75 @@ USING ( ) ); -CREATE POLICY "Allow access to card labels in user's workspace" +CREATE POLICY "Allow deleting labels in user's workspace" +ON public.label +AS PERMISSIVE +FOR DELETE +TO authenticated +USING ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +/* CARD LABELS */ +CREATE POLICY "Allow access to card labels in user's workspace or public boards" ON public._card_labels AS PERMISSIVE -FOR ALL +FOR SELECT +TO anon, authenticated +USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) +); + +CREATE POLICY "Allow inserting card labels in user's workspace" +ON public._card_labels +AS PERMISSIVE +FOR INSERT +TO authenticated +WITH CHECK ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +CREATE POLICY "Allow updating card labels in user's workspace" +ON public._card_labels +AS PERMISSIVE +FOR UPDATE TO authenticated USING ( "cardId" IN ( @@ -209,6 +453,117 @@ USING ( ) ); +CREATE POLICY "Allow deleting card labels in user's workspace" +ON public._card_labels +AS PERMISSIVE +FOR DELETE +TO authenticated +USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +/* CARD ACTIVITY */ +CREATE POLICY "Allow access to card activity in user's workspace or public boards" +ON public.card_activity +AS PERMISSIVE +FOR SELECT +TO anon, authenticated +USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) +); + +CREATE POLICY "Allow inserting card activity in user's workspace" +ON public.card_activity +AS PERMISSIVE +FOR INSERT +TO authenticated +WITH CHECK ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +/* CARD COMMENTS */ +CREATE POLICY "Allow access to card comments in user's workspace or public boards" +ON public.card_comments +AS PERMISSIVE +FOR SELECT +TO anon, authenticated +USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) +); + +CREATE POLICY "Allow inserting comments on cards in user's workspace" +ON public.card_comments +AS PERMISSIVE +FOR INSERT +TO authenticated +WITH CHECK ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) +); + +CREATE POLICY "Allow updating own comments" +ON public.card_comments +AS PERMISSIVE +FOR UPDATE +TO authenticated +USING ( + "createdBy" = auth.uid() +); + +CREATE POLICY "Allow deleting own comments" +ON public.card_comments +AS PERMISSIVE +FOR DELETE +TO authenticated +USING ( + "createdBy" = auth.uid() +); + +/* CARD WORKSPACE MEMBERS */ CREATE POLICY "Allow access to card workspace members in user's workspace" ON public._card_workspace_members AS PERMISSIVE @@ -233,6 +588,7 @@ USING ( ) ); +/* USER */ CREATE POLICY "Allow viewing members in user's workspace" ON public.user AS PERMISSIVE @@ -250,19 +606,31 @@ USING ( ) ); +/* WORKSPACE */ CREATE POLICY "Allow viewing user's workspaces" ON public.workspace AS PERMISSIVE FOR SELECT -TO authenticated +TO anon, authenticated USING ( - id IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) - OR - "createdBy" = auth.uid() + CASE + WHEN auth.uid() IS NULL THEN + -- For anonymous users, only allow access through public boards + EXISTS ( + SELECT 1 + FROM board + WHERE "workspaceId" = workspace.id + AND visibility = 'public' + ) + ELSE + -- For authenticated users, allow access to their workspaces + id IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + OR "createdBy" = auth.uid() + END ); CREATE POLICY "Allow updating user's workspaces" @@ -298,6 +666,7 @@ FOR INSERT TO authenticated USING (true); +/* WORKSPACE MEMBERS */ CREATE POLICY "Allow members to view workspace membership" ON public.workspace_members AS PERMISSIVE @@ -334,6 +703,8 @@ TO authenticated USING ( is_workspace_admin(auth.uid(), "workspaceId") ); + +/* IMPORT */ CREATE POLICY "Allow access to user's own imports" ON public.import AS PERMISSIVE @@ -341,6 +712,4 @@ FOR ALL TO authenticated USING ( "createdBy" = auth.uid() -); - - +); \ No newline at end of file diff --git a/packages/db/src/repository/board.repo.ts b/packages/db/src/repository/board.repo.ts index 55b9fbe7..ded99232 100644 --- a/packages/db/src/repository/board.repo.ts +++ b/packages/db/src/repository/board.repo.ts @@ -114,13 +114,7 @@ export const getBySlug = async ( publicId, name, slug, - description, - members:workspace_members ( - publicId, - user!workspace_members_userId_user_id_fk ( - name - ) - ) + description ), labels:label ( publicId, @@ -142,12 +136,6 @@ export const getBySlug = async ( publicId, name, colourCode - ), - members:workspace_members${filters.members.length > 0 ? "!inner" : ""} ( - publicId, - user!workspace_members_userId_user_id_fk ( - name - ) ) ) ) @@ -156,18 +144,12 @@ export const getBySlug = async ( .eq("slug", boardSlug) .is("deletedAt", null) .is("lists.deletedAt", null) - .is("lists.cards.deletedAt", null) - .is("workspace.members.deletedAt", null) - .is("lists.cards.members.deletedAt", null); + .is("lists.cards.deletedAt", null); if (filters.labels.length > 0) { query = query.in("lists.cards.labels.publicId", filters.labels); } - if (filters.members.length > 0) { - query = query.in("lists.cards.members.publicId", filters.members); - } - const { data } = await query .order("index", { foreignTable: "list", ascending: true }) .order("index", { foreignTable: "list.card", ascending: true })