Bug
deleteWorkoutSessionAction lets any authenticated user delete any other user's workout session. It's built on the unauthenticated actionClient, fetches the session's userId but never compares it to the caller, and deletes by id alone.
Root cause
src/features/workout-session/actions/delete-workout-session.action.ts:
export const deleteWorkoutSessionAction = actionClient.schema(...).action(async ({ parsedInput }) => {
const { id } = parsedInput;
const session = await prisma.workoutSession.findUnique({
where: { id },
select: { userId: true }, // ← fetched, but never compared to anyone
});
if (!session) { ... }
await prisma.workoutSession.delete({ where: { id } }); // ← deletes unconditionally
...
});
actionClient (vs authenticatedActionClient) performs no auth and exposes no ctx.user, so the action has no idea who the caller is. The session row's userId is selected and then discarded. Any logged-in user who sends a valid session id deletes it — cross-user data loss.
The codebase already has authenticatedActionClient (used by favorites/get-users) that injects ctx.user via serverAuth(); the delete action just doesn't use it.
Impact
A user can delete another user's workout history by id (guessable/enumerable cuids, leaked via leaderboard/session-share). No audit, no ownership check.
Fix
Switch to authenticatedActionClient and require ownership:
export const deleteWorkoutSessionAction = authenticatedActionClient
.schema(deleteWorkoutSessionSchema)
.action(async ({ parsedInput, ctx }) => {
const { id } = parsedInput;
const session = await prisma.workoutSession.findUnique({ where: { id }, select: { userId: true } });
if (!session || session.userId !== ctx.user.id) {
return { serverError: "Session not found" };
}
await prisma.workoutSession.delete({ where: { id } });
return { success: true };
});
Returning the same "not found" for both missing and non-owned prevents existence-disclosure. I have a PR ready.
Bug
deleteWorkoutSessionActionlets any authenticated user delete any other user's workout session. It's built on the unauthenticatedactionClient, fetches the session'suserIdbut never compares it to the caller, and deletes byidalone.Root cause
src/features/workout-session/actions/delete-workout-session.action.ts:actionClient(vsauthenticatedActionClient) performs no auth and exposes noctx.user, so the action has no idea who the caller is. The session row'suserIdis selected and then discarded. Any logged-in user who sends a valid sessioniddeletes it — cross-user data loss.The codebase already has
authenticatedActionClient(used by favorites/get-users) that injectsctx.userviaserverAuth(); the delete action just doesn't use it.Impact
A user can delete another user's workout history by id (guessable/enumerable cuids, leaked via leaderboard/session-share). No audit, no ownership check.
Fix
Switch to
authenticatedActionClientand require ownership:Returning the same "not found" for both missing and non-owned prevents existence-disclosure. I have a PR ready.