Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion apps/web/client/src/server/api/routers/project/helper.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<void> {
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');
}
}
6 changes: 5 additions & 1 deletion apps/web/client/src/server/api/routers/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand All @@ -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(),
Expand All @@ -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),
});
Expand All @@ -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),
});
Expand Down
Loading