From 0b734bd460246784a19b0df69f3ad41d6c4315aa Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 28 Jan 2026 12:03:21 +0000 Subject: [PATCH] feat: assert permission for remaining routes --- packages/api/src/routers/attachment.ts | 11 ++++------- packages/api/src/routers/checklist.ts | 25 ++++++++++++------------- packages/api/src/routers/import.ts | 5 ++--- packages/api/src/routers/label.ts | 14 +++++--------- packages/api/src/routers/member.ts | 17 ++++++++--------- packages/api/src/routers/workspace.ts | 17 ++++++----------- 6 files changed, 37 insertions(+), 52 deletions(-) diff --git a/packages/api/src/routers/attachment.ts b/packages/api/src/routers/attachment.ts index 0d4586ae..571670ae 100644 --- a/packages/api/src/routers/attachment.ts +++ b/packages/api/src/routers/attachment.ts @@ -8,7 +8,7 @@ import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { generateUID } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { assertUserInWorkspace } from "../utils/auth"; +import { assertPermission } from "../utils/permissions"; import { deleteObject, generateUploadUrl } from "../utils/s3"; export const attachmentRouter = createTRPCRouter({ @@ -55,8 +55,7 @@ export const attachmentRouter = createTRPCRouter({ message: `Card with public ID ${input.cardPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, card.workspaceId); + await assertPermission(ctx.db, userId, card.workspaceId, "card:edit"); // Get workspace publicId const workspace = await workspaceRepo.getById(ctx.db, card.workspaceId); @@ -131,8 +130,7 @@ export const attachmentRouter = createTRPCRouter({ message: `Card with public ID ${input.cardPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, card.workspaceId); + await assertPermission(ctx.db, userId, card.workspaceId, "card:edit"); const attachment = await cardAttachmentRepo.create(ctx.db, { cardId: card.id, @@ -186,8 +184,7 @@ export const attachmentRouter = createTRPCRouter({ }); const workspaceId = attachment.card.list.board.workspaceId; - - await assertUserInWorkspace(ctx.db, userId, workspaceId); + await assertPermission(ctx.db, userId, workspaceId, "card:edit"); const bucket = process.env.NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME; if (bucket) { diff --git a/packages/api/src/routers/checklist.ts b/packages/api/src/routers/checklist.ts index c4bc3ed3..0da7676a 100644 --- a/packages/api/src/routers/checklist.ts +++ b/packages/api/src/routers/checklist.ts @@ -6,7 +6,7 @@ import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo"; import * as checklistRepo from "@kan/db/repository/checklist.repo"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { assertUserInWorkspace } from "../utils/auth"; +import { assertPermission } from "../utils/permissions"; const checklistSchema = z.object({ publicId: z.string().length(12), @@ -57,8 +57,7 @@ export const checklistRouter = createTRPCRouter({ message: `Card with public ID ${input.cardPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, card.workspaceId); + await assertPermission(ctx.db, userId, card.workspaceId, "card:edit"); const newChecklist = await checklistRepo.create(ctx.db, { name: input.name, @@ -106,11 +105,11 @@ export const checklistRouter = createTRPCRouter({ message: `Checklist with public ID ${input.checklistPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace( + await assertPermission( ctx.db, userId, checklist.card.list.board.workspace.id, + "card:edit", ); const previousName = checklist.name; @@ -166,11 +165,11 @@ export const checklistRouter = createTRPCRouter({ message: `Checklist with public ID ${input.checklistPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace( + await assertPermission( ctx.db, userId, checklist.card.list.board.workspace.id, + "card:edit", ); await checklistRepo.softDeleteAllItemsByChecklistId(ctx.db, { @@ -237,11 +236,11 @@ export const checklistRouter = createTRPCRouter({ message: `Checklist with public ID ${input.checklistPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace( + await assertPermission( ctx.db, userId, checklist.card.list.board.workspace.id, + "card:edit", ); const newChecklistItem = await checklistRepo.createItem(ctx.db, { @@ -304,11 +303,11 @@ export const checklistRouter = createTRPCRouter({ message: `Checklist item with public ID ${input.checklistItemPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace( + await assertPermission( ctx.db, userId, item.checklist.card.list.board.workspace.id, + "card:edit", ); const previousTitle = item.title; @@ -394,11 +393,11 @@ export const checklistRouter = createTRPCRouter({ message: `Checklist item with public ID ${input.checklistItemPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace( + await assertPermission( ctx.db, userId, item.checklist.card.list.board.workspace.id, + "card:edit", ); const deleted = await checklistRepo.softDeleteItemById(ctx.db, { diff --git a/packages/api/src/routers/import.ts b/packages/api/src/routers/import.ts index a687b1bb..62b5ecbd 100644 --- a/packages/api/src/routers/import.ts +++ b/packages/api/src/routers/import.ts @@ -14,7 +14,7 @@ import { colours } from "@kan/shared/constants"; import { generateUID } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { assertUserInWorkspace } from "../utils/auth"; +import { assertPermission } from "../utils/permissions"; import { apiKeys, urls } from "./integration"; export interface TrelloBoard { @@ -180,8 +180,7 @@ export const importRouter = createTRPCRouter({ message: `Workspace with public ID ${input.workspacePublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, workspace.id); + await assertPermission(ctx.db, userId, workspace.id, "board:create"); const newImport = await importRepo.create(ctx.db, { source: "trello", diff --git a/packages/api/src/routers/label.ts b/packages/api/src/routers/label.ts index ea5506d0..04068723 100644 --- a/packages/api/src/routers/label.ts +++ b/packages/api/src/routers/label.ts @@ -6,7 +6,7 @@ import * as cardRepo from "@kan/db/repository/card.repo"; import * as labelRepo from "@kan/db/repository/label.repo"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { assertUserInWorkspace } from "../utils/auth"; +import { assertPermission } from "../utils/permissions"; const labelSchema = z.object({ publicId: z.string(), @@ -47,8 +47,7 @@ export const labelRouter = createTRPCRouter({ message: `Label with public ID ${input.labelPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, label.workspaceId); + await assertPermission(ctx.db, userId, label.workspaceId, "board:view"); const result = await labelRepo.getByPublicId(ctx.db, input.labelPublicId); @@ -102,8 +101,7 @@ export const labelRouter = createTRPCRouter({ message: `Board with public ID ${input.boardPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, board.workspaceId); + await assertPermission(ctx.db, userId, board.workspaceId, "board:edit"); const result = await labelRepo.create(ctx.db, { name: input.name, @@ -162,8 +160,7 @@ export const labelRouter = createTRPCRouter({ message: `Label with public ID ${input.labelPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, label.workspaceId); + await assertPermission(ctx.db, userId, label.workspaceId, "board:edit"); const result = await labelRepo.update(ctx.db, input); @@ -211,8 +208,7 @@ export const labelRouter = createTRPCRouter({ message: `Label with public ID ${input.labelPublicId} not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, label.workspaceId); + await assertPermission(ctx.db, userId, label.workspaceId, "board:edit"); await cardRepo.hardDeleteAllCardLabelRelationships(ctx.db, label.id); diff --git a/packages/api/src/routers/member.ts b/packages/api/src/routers/member.ts index 35625f2f..27d92ca3 100644 --- a/packages/api/src/routers/member.ts +++ b/packages/api/src/routers/member.ts @@ -16,7 +16,6 @@ import { import { updateSubscriptionSeats } from "@kan/stripe"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; -import { assertUserInWorkspace } from "../utils/auth"; import { assertCanManageMember, assertCanManageRole, @@ -62,7 +61,7 @@ export const memberRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertUserInWorkspace(ctx.db, userId, workspace.id, "admin"); + await assertPermission(ctx.db, userId, workspace.id, "member:invite"); const isInvitedEmailAlreadyMember = workspace.members.some( (member) => member.email === input.email, @@ -196,7 +195,7 @@ export const memberRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertUserInWorkspace(ctx.db, userId, workspace.id, "admin"); + await assertPermission(ctx.db, userId, workspace.id, "member:remove"); const member = await memberRepo.getByPublicId( ctx.db, @@ -298,8 +297,8 @@ export const memberRouter = createTRPCRouter({ code: "NOT_FOUND", }); - // Check if user is in workspace - await assertUserInWorkspace(ctx.db, userId, workspace.id); + // Check if user can view members + await assertPermission(ctx.db, userId, workspace.id, "member:view"); // Get active invite link for this workspace const activeInviteLink = await inviteLinkRepo.getActiveForWorkspace( @@ -366,8 +365,8 @@ export const memberRouter = createTRPCRouter({ code: "NOT_FOUND", }); - // Check if user is in workspace - await assertUserInWorkspace(ctx.db, userId, workspace.id, "admin"); + // Check if user can edit members (admin-equivalent) + await assertPermission(ctx.db, userId, workspace.id, "member:edit"); // Check subscription for cloud environment if (process.env.NEXT_PUBLIC_KAN_ENV === "cloud") { @@ -467,8 +466,8 @@ export const memberRouter = createTRPCRouter({ code: "NOT_FOUND", }); - // Check if user is in workspace - await assertUserInWorkspace(ctx.db, userId, workspace.id, "admin"); + // Check if user can edit members (admin-equivalent) + await assertPermission(ctx.db, userId, workspace.id, "member:edit"); // Deactivate all active invite links await inviteLinkRepo.deactivateAllActiveForWorkspace(ctx.db, { diff --git a/packages/api/src/routers/workspace.ts b/packages/api/src/routers/workspace.ts index 26060032..569c1b44 100644 --- a/packages/api/src/routers/workspace.ts +++ b/packages/api/src/routers/workspace.ts @@ -7,7 +7,7 @@ import * as workspaceSlugRepo from "@kan/db/repository/workspaceSlug.repo"; import { generateUID } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; -import { assertUserInWorkspace } from "../utils/auth"; +import { assertPermission } from "../utils/permissions"; export const workspaceRouter = createTRPCRouter({ all: protectedProcedure @@ -74,8 +74,7 @@ export const workspaceRouter = createTRPCRouter({ message: `Workspace not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, result.id); + await assertPermission(ctx.db, userId, result.id, "workspace:view"); // Check if user is an admin const userMember = result.members.find( @@ -164,8 +163,7 @@ export const workspaceRouter = createTRPCRouter({ message: `Workspace not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, result.id); + await assertPermission(ctx.db, userId, result.id, "workspace:view"); return result; }), @@ -296,8 +294,7 @@ export const workspaceRouter = createTRPCRouter({ message: `Workspace not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, workspace.id, "admin"); + await assertPermission(ctx.db, userId, workspace.id, "workspace:edit"); if (input.slug) { const reservedOrPremiumWorkspaceSlug = @@ -379,8 +376,7 @@ export const workspaceRouter = createTRPCRouter({ message: `Workspace not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, workspace.id, "admin"); + await assertPermission(ctx.db, userId, workspace.id, "workspace:delete"); const result = await workspaceRepo.hardDelete( ctx.db, @@ -518,8 +514,7 @@ export const workspaceRouter = createTRPCRouter({ message: `Workspace not found`, code: "NOT_FOUND", }); - - await assertUserInWorkspace(ctx.db, userId, workspace.id); + await assertPermission(ctx.db, userId, workspace.id, "workspace:view"); const result = await workspaceRepo.searchBoardsAndCards( ctx.db,