fix: PATCH /api/tasks/:id geeft 403 bij cross-user toegang

Vervang productAccessFilter in de WHERE clause door een expliciete
toegangscheck na het ophalen. findFirst haalt de taak op met product
en members (gefilterd op auth.userId); daarna wordt eigenaarschap of
teamlidmaatschap gecontroleerd en 403 teruggegeven bij geen toegang.

Dit herstelt het onderscheid 404 (taak bestaat niet) vs 403 (taak
bestaat maar geen toegang), zoals de beveiligingstest verwacht.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-25 13:44:51 +02:00
parent e94959c5bc
commit d90a8fd560

View file

@ -1,6 +1,5 @@
import { authenticateApiRequest } from '@/lib/api-auth' import { authenticateApiRequest } from '@/lib/api-auth'
import { prisma } from '@/lib/prisma' import { prisma } from '@/lib/prisma'
import { productAccessFilter } from '@/lib/product-access'
import { z } from 'zod' import { z } from 'zod'
const patchSchema = z const patchSchema = z
@ -27,12 +26,33 @@ export async function PATCH(
const { id } = await params const { id } = await params
const task = await prisma.task.findFirst({ const task = await prisma.task.findFirst({
where: { id, story: { product: productAccessFilter(auth.userId) } }, where: { id },
include: {
story: {
include: {
product: {
include: {
members: {
where: { user_id: auth.userId },
select: { id: true },
},
},
},
},
},
},
}) })
if (!task) { if (!task) {
return Response.json({ error: 'Taak niet gevonden' }, { status: 404 }) return Response.json({ error: 'Taak niet gevonden' }, { status: 404 })
} }
const hasAccess =
task.story.product.user_id === auth.userId ||
(task.story.product.members?.length ?? 0) > 0
if (!hasAccess) {
return Response.json({ error: 'Geen toegang' }, { status: 403 })
}
const body = await request.json().catch(() => null) const body = await request.json().catch(() => null)
const parsed = patchSchema.safeParse(body) const parsed = patchSchema.safeParse(body)
if (!parsed.success) { if (!parsed.success) {