Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
30 changes: 29 additions & 1 deletion apps/web/client/src/server/api/routers/project/helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Frame } from "@onlook/db";
import { eq } from "drizzle-orm";
import { type Frame, projects, userProjects, type DrizzleDb } from "@onlook/db";

export function extractCsbPort(frames: Frame[]): number | null {
if (!frames || frames.length === 0) return null;
Expand All @@ -17,3 +18,30 @@ export function extractCsbPort(frames: Frame[]): number | null {
}
return null;
}

/**
* Verifies that a user has access to a project by checking the userProjects table.
* @throws Error if the user does not have access to the project or if it doesn't exist
*
* Note: This function intentionally returns the same error message whether the project
* doesn't exist or the user lacks access to prevent information disclosure about
* project existence.
*/
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 || project.userProjects.length === 0) {
throw new Error('Unauthorized or not found');
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
7 changes: 6 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 All @@ -58,6 +58,7 @@ export const projectRouter = createTRPCRouter({
captureScreenshot: protectedProcedure
.input(z.object({ projectId: z.string() }))
.mutation(async ({ ctx, input }) => {
await verifyProjectAccess(ctx.db, ctx.user.id, input.projectId);
try {
if (!env.FIRECRAWL_API_KEY) {
throw new Error('FIRECRAWL_API_KEY is not configured');
Expand Down Expand Up @@ -348,6 +349,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 +367,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 +383,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 +408,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