Skip to content

fix(events): match mine:true by auth.id instead of empty-string username fallback - #60

Open
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-events-match-mine-true-by-auth-id-instead-of-e-a7812e
Open

detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-events-match-mine-true-by-auth-id-instead-of-e-a7812e

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Bug

EventsWhereInput.toQuery(auth) (consumed by the events query) built the mine: true clause by reading auth.username || '' for the mentor, manager, and student branches. But every mentor/student token ever issued by this repo is ID-target (tgt: AuthByTarget.ID, sid: <user-id> via signTokenUser), for which AuthContext.username is undefined and AuthContext.id holds the user id. So for every real caller the predicate emitted username: "" / managerUsername: "", which:

  • Never matched the caller's own row (the onboarding flow creates rows with a non-empty username) → mine: true returned empty for the logged-in mentor/student.
  • Matched any other user's Mentor/Student row whose username = "" (which any mentor/student can self-clear via editMentor/editStudent's falsy guards) → cross-user misattribution of events.

EventsWhereInput was the only call site reading auth.username directly instead of dispatching via idOrUsernameOrAuthToUniqueWhere.

Fix

In src/inputs/EventsWhereInput.ts, the mine clause now matches by auth.id and auth.username with no || '' fallback, and the managerUsername branch is only emitted when auth.username is defined:

this.mine ? {
  OR: [
    { mentors: { some: { OR: [{ id: auth.id }, { username: auth.username }] } } },
    { students: { some: { OR: [{ id: auth.id }, { username: auth.username }] } } },
    ...(auth.username ? [{ mentors: { some: { managerUsername: auth.username } } } as Prisma.EventWhereInput] : []),
  ],
} : {},

For the issued ID-target tokens this reduces to { mentors/students: { some: { id: auth.id } } } (Prisma strips undefined keys), matching only the caller's own rows and no username = "" row. The managerUsername branch is guarded because leaving a bare managerUsername: undefined collapses to {mentors:{some:{}}} = "any event with a mentor", which I confirmed empirically would leak events; guarding it on auth.username keeps it for the designed-for username-target manager path while closing that leak for ID-target and unauthenticated callers.

Testing

  • Added tests/testEventsWhereInput.ts, a DB-less suite (repo's node:test + node:assert/strict conventions) asserting the toQuery() predicate shape across five callers: ID-target mentor and student (via the real signTokenUser), username-target mentor and manager (hand-minted JWTs exercising the designed-for branch), and an unauthenticated caller. It verifies the mine OR emits id for ID-target tokens, omits the manager branch when auth.username is undefined, and never contains a username:"" / managerUsername:"" fallback. Also covers mine unset and composition with public/partnerCode.
  • Typecheck, the new unit suite, and the existing tests/testSlackReporting.ts all pass; the production diff is limited to src/inputs/EventsWhereInput.ts (3 lines) plus the new test, so the other resolvers that dispatch via idOrUsernameOrAuthToUniqueWhere are untouched and unaffected.
  • End-to-end verification against a live PostgreSQL instance (Docker postgres:15-alpine, schema applied via prisma db push because prisma migrate deploy fails on a fresh DB due to a pre-existing repo migration-history inconsistency — 20220610164033_add_event_id_to_student_mentor_project drops an index no earlier migration creates). Seeded events with an ID-target mentor, an ID-target student, and a username:"" mentor (the misattribution seed), then queried through the repo's real PrismaClient + EventsWhereInput.toQuery() + signTokenUser: the ID-target mentor received only their own event (not the username:"" row's event), a different ID-target mentor did not receive the username:"" row's event (no cross-user misattribution), and an unauthenticated mine: true call returned []. This DB-backed test was not versioned since it requires a live database the repo's CI (docker build only) does not provision.
  • Lint (npx eslint) hits a pre-existing environmental failure: @typescript-eslint/parser (via the pinned @codeday/eslint-typescript-config on eslint 7) is incompatible with the installed TypeScript 5.2.2 and errors with DeprecationError: 'originalKeywordKind' before any rule runs. The identical error reproduces on the untouched tests/testSlackReporting.ts, so it is not introduced by this change; CI does not run lint. Resolving it is a separate tooling-upgrade task.

Automatic Fixes PRs can be configured here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant