Scrum4Me/app/api/tasks/[id]/route.ts
janpeter visser d90a8fd560 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>
2026-04-25 13:44:51 +02:00

77 lines
2.1 KiB
TypeScript

import { authenticateApiRequest } from '@/lib/api-auth'
import { prisma } from '@/lib/prisma'
import { z } from 'zod'
const patchSchema = z
.object({
status: z.enum(['TO_DO', 'IN_PROGRESS', 'DONE']).optional(),
implementation_plan: z.string().optional(),
})
.refine((data) => data.status !== undefined || data.implementation_plan !== undefined, {
message: 'Geef minimaal status of implementation_plan mee',
})
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = await authenticateApiRequest(request)
if ('error' in auth) {
return Response.json({ error: auth.error }, { status: auth.status })
}
if (auth.isDemo) {
return Response.json({ error: 'Niet beschikbaar in demo-modus' }, { status: 403 })
}
const { id } = await params
const task = await prisma.task.findFirst({
where: { id },
include: {
story: {
include: {
product: {
include: {
members: {
where: { user_id: auth.userId },
select: { id: true },
},
},
},
},
},
},
})
if (!task) {
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 parsed = patchSchema.safeParse(body)
if (!parsed.success) {
return Response.json({ error: parsed.error.flatten() }, { status: 400 })
}
const updated = await prisma.task.update({
where: { id },
data: {
...(parsed.data.status !== undefined && { status: parsed.data.status }),
...(parsed.data.implementation_plan !== undefined && {
implementation_plan: parsed.data.implementation_plan,
}),
},
})
return Response.json({
id: updated.id,
status: updated.status,
implementation_plan: updated.implementation_plan,
})
}