FRE-588: Fix IDOR vulnerabilities and security findings
H1: Add verifyScriptAccess/verifyRevisionAccess to all 14 revisions endpoints H2: Add verifyProjectAccess to listScripts and searchScripts M2: Add cascade delete for projectMembers on project deletion M4: Replace plain Error throws with TRPCError for consistent error handling M5: Use crypto.randomUUID for team ID generation (was Date.now + Math.random) L1: Add 100KB content size limit on revision content L2: Add unique constraint to script slug column L3: Update hasProjectAccess middleware to check project membership
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { initTRPC, TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { projects } from '../../src/db/schema';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import { projects, projectMembers } from '../../src/db/schema';
|
||||
import type { TRPCContext } from './types';
|
||||
|
||||
// Initialize tRPC with context
|
||||
@@ -61,7 +61,14 @@ const hasProjectAccess = t.middleware(async ({ ctx, next }) => {
|
||||
if (!project) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${ctx.projectId} not found` });
|
||||
}
|
||||
if (project.ownerId !== dbUser.id) {
|
||||
if (project.ownerId === dbUser.id) {
|
||||
return next({ ctx: { ...ctx, projectId: ctx.projectId, userId: dbUser.id } });
|
||||
}
|
||||
// L3 fix: also check project membership
|
||||
const memberRows = await ctx.db.select()
|
||||
.from(projectMembers)
|
||||
.where(and(eq(projectMembers.projectId, ctx.projectId), eq(projectMembers.userId, dbUser.id)));
|
||||
if (memberRows.length === 0) {
|
||||
throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to project ${ctx.projectId}` });
|
||||
}
|
||||
return next({ ctx: { ...ctx, projectId: ctx.projectId, userId: dbUser.id } });
|
||||
|
||||
Reference in New Issue
Block a user