diff --git a/apps/web/client/src/server/api/routers/project/helper.ts b/apps/web/client/src/server/api/routers/project/helper.ts index e2f6037378..387f78f25f 100644 --- a/apps/web/client/src/server/api/routers/project/helper.ts +++ b/apps/web/client/src/server/api/routers/project/helper.ts @@ -1,5 +1,12 @@ -import { type Frame } from "@onlook/db"; +import { eq } from "drizzle-orm"; +import { type Frame, projects, userProjects, type DrizzleDb } from "@onlook/db"; +/** + * Extracts the CodeSandbox preview port from an array of frames. + * + * @param frames - Array of frames to search for a CodeSandbox preview URL + * @returns The extracted port number if a matching CodeSandbox preview URL is found, `null` otherwise + */ export function extractCsbPort(frames: Frame[]): number | null { if (!frames || frames.length === 0) return null; @@ -17,3 +24,34 @@ export function extractCsbPort(frames: Frame[]): number | null { } return null; } + +/** + * Ensure the specified user has access to the specified project. + * + * @param userId - ID of the user to check + * @param projectId - ID of the project to check + * @throws Error with message 'Project not found' if no project exists with `projectId` + * @throws Error with message 'Unauthorized: You do not have access to this project' if the user has no access to the project + */ +export async function verifyProjectAccess( + db: DrizzleDb, + userId: string, + projectId: string, +): Promise { + const project = await db.query.projects.findFirst({ + where: eq(projects.id, projectId), + with: { + userProjects: { + where: eq(userProjects.userId, userId), + }, + }, + }); + + if (!project) { + throw new Error('Project not found'); + } + + if (project.userProjects.length === 0) { + throw new Error('Unauthorized: You do not have access to this project'); + } +} \ No newline at end of file diff --git a/apps/web/client/src/server/api/routers/project/project.ts b/apps/web/client/src/server/api/routers/project/project.ts index a110db5bbf..2747860a92 100644 --- a/apps/web/client/src/server/api/routers/project/project.ts +++ b/apps/web/client/src/server/api/routers/project/project.ts @@ -37,7 +37,7 @@ import { and, eq, ne } from 'drizzle-orm'; import { z } from 'zod'; import { projectCreateRequestRouter } from './createRequest'; import { fork } from './fork'; -import { extractCsbPort } from './helper'; +import { extractCsbPort, verifyProjectAccess } from './helper'; export const projectRouter = createTRPCRouter({ hasAccess: protectedProcedure @@ -348,6 +348,7 @@ export const projectRouter = createTRPCRouter({ delete: protectedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { + await verifyProjectAccess(ctx.db, ctx.user.id, input.id); await ctx.db.transaction(async (tx) => { await tx.delete(projects).where(eq(projects.id, input.id)); await tx.delete(userProjects).where(eq(userProjects.projectId, input.id)); @@ -365,6 +366,7 @@ export const projectRouter = createTRPCRouter({ return projects.map((project) => fromDbProject(project.project)); }), update: protectedProcedure.input(projectUpdateSchema).mutation(async ({ ctx, input }) => { + await verifyProjectAccess(ctx.db, ctx.user.id, input.id); const [updatedProject] = await ctx.db.update(projects).set({ ...input, updatedAt: new Date(), @@ -380,6 +382,7 @@ export const projectRouter = createTRPCRouter({ projectId: z.string(), tag: z.string(), })).mutation(async ({ ctx, input }) => { + await verifyProjectAccess(ctx.db, ctx.user.id, input.projectId); const project = await ctx.db.query.projects.findFirst({ where: eq(projects.id, input.projectId), }); @@ -404,6 +407,7 @@ export const projectRouter = createTRPCRouter({ projectId: z.string(), tag: z.string(), })).mutation(async ({ ctx, input }) => { + await verifyProjectAccess(ctx.db, ctx.user.id, input.projectId); const project = await ctx.db.query.projects.findFirst({ where: eq(projects.id, input.projectId), });