Bug: session-progress/start lets a session from a different program overwrite the user enrollment pointer
Bug
POST /api/programs/session-progress/start creates a UserSessionProgress for any session id the client passes, without checking that the session belongs to the program the enrollment is for. The user's enrollment pointer (currentWeek / currentSession) is then overwritten with numbers from that unrelated session.
Root cause
In app/api/programs/session-progress/start/route.ts:
const enrollment = await prisma.userProgramEnrollment.findFirst({
where: { id: enrollmentId, userId },
...
});
...
const programSession = await prisma.programSession.findUnique({
where: { id: sessionId }, // ← no programId constraint
...
});
...
const sessionProgress = await prisma.userSessionProgress.create({
data: { enrollmentId, sessionId },
});
await prisma.userProgramEnrollment.update({
where: { id: enrollmentId },
data: {
currentWeek: programSession.week.weekNumber, // ← from the OTHER program
currentSession: programSession.sessionNumber, // ← from the OTHER program
},
});The enrollment captures programId (program A), but programSession.week.programId (program B) is never compared against it. Nothing in the schema enforces a program match either: UserSessionProgress.session only relates to ProgramSession, not to the enrollment's program.
So a request like { enrollmentId: <my enrollment in Program A>, sessionId: <session from Program B> } succeeds and:
- creates a
UserSessionProgresslinking my Program-A enrollment to a Program-B session, - sets my enrollment's
currentWeek/currentSessionto Program-B's values, - breaks the user's "current position" in the program they actually enrolled in (and the
completeroute inherits the wrong row).
It's effectively a cross-program data-integrity / authorization-scope gap.
Fix
Scope the session lookup to the enrollment's program and reject mismatches:
const programSession = await prisma.programSession.findUnique({
where: { id: sessionId },
include: { week: true, ... },
});
if (!programSession || programSession.week.programId !== enrollment.programId) {
return NextResponse.json({ error: "Session not found" }, { status: 404 });
}I have a PR ready.
Source: Snouzy/workout-cool