Skip to content

Bug: session-progress/start lets a session from a different program overwrite the user enrollment pointer #230

Description

@evan188199-tech

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:

  1. creates a UserSessionProgress linking my Program-A enrollment to a Program-B session,
  2. sets my enrollment's currentWeek / currentSession to Program-B's values,
  3. breaks the user's "current position" in the program they actually enrolled in (and the complete route 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions