diff --git a/src/server/routers/evaluation.ts b/src/server/routers/evaluation.ts index 2381f5a..7748ee8 100644 --- a/src/server/routers/evaluation.ts +++ b/src/server/routers/evaluation.ts @@ -670,11 +670,24 @@ export const evaluationRouter = router({ }), /** - * Get COI status for an assignment + * Get COI status for an assignment. + * Caller must own the assignment (or be admin). The COI description can + * contain confidential personal/financial relationships. */ getCOIStatus: protectedProcedure .input(z.object({ assignmentId: z.string() })) .query(async ({ ctx, input }) => { + const assignment = await ctx.prisma.assignment.findUnique({ + where: { id: input.assignmentId }, + select: { userId: true }, + }) + if (!assignment) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Assignment not found' }) + } + const isAdmin = userHasRole(ctx.user, 'SUPER_ADMIN', 'PROGRAM_ADMIN') + if (!isAdmin && assignment.userId !== ctx.user.id) { + throw new TRPCError({ code: 'FORBIDDEN' }) + } return ctx.prisma.conflictOfInterest.findUnique({ where: { assignmentId: input.assignmentId }, }) @@ -1085,7 +1098,8 @@ export const evaluationRouter = router({ }), /** - * Get or create a discussion for a project evaluation + * Get or create a discussion for a project evaluation. + * Caller must have an Assignment for this project+round (or be admin). */ getDiscussion: juryProcedure .input( @@ -1095,6 +1109,24 @@ export const evaluationRouter = router({ }) ) .query(async ({ ctx, input }) => { + // Authorization: admins always allowed; jurors must have an Assignment. + if (!userHasRole(ctx.user, 'SUPER_ADMIN', 'PROGRAM_ADMIN')) { + const assignment = await ctx.prisma.assignment.findFirst({ + where: { + userId: ctx.user.id, + projectId: input.projectId, + roundId: input.roundId, + }, + select: { id: true }, + }) + if (!assignment) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'You are not assigned to this project', + }) + } + } + // Get or create discussion let discussion = await ctx.prisma.evaluationDiscussion.findUnique({ where: { @@ -1171,7 +1203,8 @@ export const evaluationRouter = router({ }), /** - * Add a comment to a project evaluation discussion + * Add a comment to a project evaluation discussion. + * Caller must have an Assignment for this project+round (or be admin). */ addComment: juryProcedure .input( @@ -1182,6 +1215,24 @@ export const evaluationRouter = router({ }) ) .mutation(async ({ ctx, input }) => { + // Authorization: admins always allowed; jurors must have an Assignment. + if (!userHasRole(ctx.user, 'SUPER_ADMIN', 'PROGRAM_ADMIN')) { + const assignment = await ctx.prisma.assignment.findFirst({ + where: { + userId: ctx.user.id, + projectId: input.projectId, + roundId: input.roundId, + }, + select: { id: true }, + }) + if (!assignment) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: 'You are not assigned to this project', + }) + } + } + // Check max comment length from round settings const round = await ctx.prisma.round.findUniqueOrThrow({ where: { id: input.roundId },