Skip to content
Merged
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
35 changes: 34 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,8 @@
import { type Frame } from "@onlook/db";
import { eq } from "drizzle-orm";
import { type Frame, projects, userProjects, type DrizzleDb } from "@onlook/db";

/** Type representing a db instance or transaction that has query capabilities */
type DbOrTx = Pick<DrizzleDb, 'query'>;

export function extractCsbPort(frames: Frame[]): number | null {
if (!frames || frames.length === 0) return null;
Expand All @@ -17,3 +21,32 @@ 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.
*
* Accepts either a db instance or a transaction to support atomic authorization checks.
*/
export async function verifyProjectAccess(
db: DbOrTx,
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.
9 changes: 7 additions & 2 deletions 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 @@ -59,6 +59,7 @@ export const projectRouter = createTRPCRouter({
.input(z.object({ projectId: z.string() }))
.mutation(async ({ ctx, input }) => {
try {
await verifyProjectAccess(ctx.db, ctx.user.id, input.projectId);
if (!env.FIRECRAWL_API_KEY) {
throw new Error('FIRECRAWL_API_KEY is not configured');
}
Expand Down Expand Up @@ -349,8 +350,9 @@ export const projectRouter = createTRPCRouter({
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
await ctx.db.transaction(async (tx) => {
await tx.delete(projects).where(eq(projects.id, input.id));
await verifyProjectAccess(tx, ctx.user.id, input.id);
await tx.delete(userProjects).where(eq(userProjects.projectId, input.id));
await tx.delete(projects).where(eq(projects.id, input.id));
});
}),
getPreviewProjects: protectedProcedure
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