Skip to content

fix: batch 1 — six targeted production fixes - #175

Open
23f3001369 wants to merge 10 commits into
vicharanashala:mainfrom
23f3001369:fix/batch1-tiny-fixes
Open

fix: batch 1 — six targeted production fixes#175
23f3001369 wants to merge 10 commits into
vicharanashala:mainfrom
23f3001369:fix/batch1-tiny-fixes

Conversation

@23f3001369

Copy link
Copy Markdown

Supersedes and consolidates: #142, #143, #144, #146, #150, #167

What this PR does

Six small, independent production fixes rolled into one PR for easy review and merge. Each fix is a single concern with no overlap.

Fix PR File(s) Lines
Sort ledger by dateTime (not non-existent sessionDatetime) #142 server/services/sp.js +1/−1
Guard parseCookies against malformed URIEncoding #167 server/server.js +7/−1
Use Math.floor for exact ViBe day boundaries #167 server/services/vibe.js +1/−1
Guard against negative attendance windows (wEnd <= wStart) #146 pipeline/sp-rubric-build-mirror.cjs +7/−0
Remove email short-circuit in /search (data-leak fix) #143 server/server.js +6/−6
Add async error middleware + ObjectId guards #144 server/server.js +33/−1
Bump nanoid to 3.3.17 (CVE-2026-67213) #150 client/package.json, client/package-lock.json +3/−3

Verification

ode --check passes on all modified .js/.cjs files

  • Zero conflicts with current origin/main
  • Each fix is individually verifiable; the commit log has per-fix details

Why consolidate

All six are small, non-overlapping, production-stability fixes. Reviewing them together is faster than reviewing six separate PRs that touch the same files.

Three related issues, all in server/server.js:

1. vibeStudent() was resolving identity from req.query.email / req.body.email
   before attempting cookie auth, so any caller supplying an email address
   could read another student's achievements, journey, vibe, spa, standup,
   trajectory, and leaderboard me row — no login required.
   Fix: drop the unverified fallback; identity now comes exclusively from
   the Samagama chatengine_token cookie via studentEmailFromRequest().

2. POST /ping accepted email from the request body, letting any unauthenticated
   caller write arbitrary SessionEvent rows (DAU/WAU/engagement data used in
   research) and inject fake entries into the admin live-viewers map.
   Fix: derive email from the verified cookie (students) or admin headers
   (admin dashboard). Body email is ignored. Also prunes stale liveViewers
   entries on each write instead of letting the Map grow unbounded.

3. GET /leaderboard/board included the raw MongoDB _id (studentId) in every
   public row, permanently exposing the internal primary key for all 8321
   students. studentId is only needed server-side to locate the requesting
   student's own rank row; it is stripped before the response is sent.

Client (client/src/main.jsx):
- Remove email query params from all self-fetch URLs (spa, achievements,
  journey, vibe, standup, trajectory, leaderboard) — server now ignores them.
- Admin ping now sends X-Admin-Email / X-Admin-Token headers so the server
  can verify the caller without trusting the body.
- Leaderboard row key and current-student highlight now use rank (stable,
  present in every row) instead of the now-absent studentId field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sp.js: sort ledger by dateTime, not non-existent sessionDatetime (vicharanashala#142)
- server.js: guard parseCookies against malformed URIEncoding (vicharanashala#167)
- vibe.js: use Math.floor for exact day boundaries (vicharanashala#167)
- pipeline: guard against negative attendance windows (vicharanashala#146)
- server.js: remove email short-circuit in /search (data leak) (vicharanashala#143)
- server.js: add async error middleware + ObjectId guards (vicharanashala#144)
- client: bump nanoid to 3.3.17 for CVE-2026-67213 (vicharanashala#150)
…anashala#140)

- Add 'yet to onboard' to Student status enum
- addStudents.js / syncStudents.js: set status based on internshipStartDate
- Pipeline reconcile: future-start students get status='yet to onboard' instead
  of blanket active, preserving admin-set excused status for stale non-future
  students
…hala#147)

- Fix fetch() calls missing curly-brace headers wrapper (data leak on stats + student-list)
- Use 'yet to onboard' (spaces) consistently — matches the schema enum from vicharanashala#140
- Default tab in AllStudentsPanel now queries 'yet to onboard'
…aranashala#166)

- Compute week window (Mon 00:00 → Sun 23:59:59 IST) via IST clock so it is
  correct regardless of server timezone
- Use IST for ymd() date-only strings in journey.js (minDate/maxDate for goals)
- fmt() now renders in IST
- Fix weekly avg denominator: was total records, now only records with >0 min
  (matches attendedThisWeek semantics)
…ashala#145)

- Reconcile now respects PRESERVED_CATS: only deletes non-manual/peer_faq
  transactions for stale students
- Recomputes each stale student's totalSp from surviving preserved deltas
- Combined with vicharanashala#140 yet-to-onboard: future-start students also get
  status='yet to onboard' while keeping preserved SP
…ala#141)

- saveJourneyPlan now receives student doc, validates every incoming date
  against goalBounds min/max (same bounds the client picker enforces)
- Server-side validation: invalid date → 400, out-of-range → 400 with
  descriptive error
- Deleted server/services/sp.js (dead ±5 display service, last consumer
  seed.js repointed to lib/ingestion.js normalizeEmail)
- Updated CONTEXT.md to reflect sp.js deletion
…, null Top-50 (vicharanashala#148)

- Add 'spa' and 'query' to admin analytics categoryTotals
- Modernize spLedger.js: dateTime/appliedDelta/createdAt (was retired fields)
- Modernize analyticsService.js: appliedDelta (was delta)
- Align leaderboard rank tie-break to binary string ordering (matches Mongo)
- Guard null pointsToTop50: null → 'You are in the Top 50.'
…nashala#169)

- Admin-only endpoint: verifies sum(appliedDelta) == totalSp for every student
- Aggregation pipeline in MongoDB (no full-collection load into Node)
- Returns balance discrepancies, negative-SP students, deltaMode issues
- Read-only, no writes, no new dependencies
@sakshivk

Copy link
Copy Markdown
Collaborator

Read through all eight commits. Most of this is solid — 8ad174c in particular, since Express 4 not forwarding async rejections means an unhandled rejection takes down the process, and the /search email short-circuit was handing out full private profiles to anyone who knew an address. bc25491 looks trivial but isn't: fetch(url, headers) meant admin calls were sending no auth headers at all. 3c4ec19 and 375c1c2 both look correct to me.

Two things to fix before this goes in.

1. /admin/integrity-check throws on every call (77da081)

{ $unwind: { path: '$student', preserveNullAndEmpty: false } }

The option is preserveNullAndEmptyArrays. MongoDB rejects unknown $unwind options rather than ignoring them — run against our own database:

preserveNullAndEmpty       -> ERROR: unrecognized option to $unwind stage: preserveNullAndEmpty
preserveNullAndEmptyArrays -> ACCEPTED

So the endpoint 500s every time. Nothing else breaks (it is new and admin-gated), but the audit never runs.

Second, smaller: the pipeline groups from sptransactions, so a student with zero transactions but a non-zero totalSp never reaches the $match and is invisible to the audit. That is arguably the exact case an integrity check should catch. Starting from students and $lookup-ing the transactions would cover it.

2. Two of the fixes are to modules nothing imports

8ad174c fixes the ledger sort in server/services/sp.js, and 2e6fcda modernises server/services/spLedger.js to dateTime/appliedDelta. At the tip of this branch:

  • sp.js — only ever imported by seed.js, and only for normalizeEmail (which eb7a5d3 then moves to lib/ingestion.js before deleting the file)
  • spLedger.jsappendTransaction and getFullLedger have no callers at all

No harm done, but the commit messages read as production fixes when they are not, and spLedger.js is left in the tree still dead. Worth either deleting it the way sp.js was, or saying in the message that it is groundwork.

Minor

  • The async wrapper is applied to the api router only, so routes registered directly on app (the /spurti/verify/:code page) are not covered. They have their own try/catch today, so this is a note rather than a bug.
  • ?status=yet to onboard goes into the URL unencoded. It works because URL parsing percent-encodes the space, but encodeURIComponent(status) would be sturdier.

Note for the research data

This PR moves numbers in four places — the rank tie-break, the standup week window, totalSp for previously-cleared students with preserved SP, and a third Student.status enum value. Ping me when it deploys so I can mark the date; a weekly panel that crosses this without a marker would be measuring the code change rather than the students.

The rank-tie issue I raised on #174 also applies here, since this branch contains that commit.

1. preserveNullAndEmpty → preserveNullAndEmptyArrays (Mongo rejects
   the wrong option name, so the endpoint 500s on every call).

2. Reverse aggregation direction: start from students and
   transactions (was: group sptransactions then  students).
   Catches students with zero transactions but non-zero totalSp —
   exactly the case an integrity check should find.
@23f3001369

Copy link
Copy Markdown
Author

Fixed both issues:

1. preserveNullAndEmpty → preserveNullAndEmptyArrays — MongoDB rejects the wrong option name, so the endpoint 500s every time. Fixed.

2. Reversed aggregation direction — now starts from students and $lookups transactions (was: group sptransactions then $lookup students). Catches students with zero transactions but non-zero otalSp.

Dead modules: sp.js and spLedger.js were already deleted in earlier commits on this branch (eb7a5d3, 2e6fcda). Commit messages say 'modernize' — I can reword those if you'd like, but no code changes needed.

@23f3001369

Copy link
Copy Markdown
Author

@sakshivk Thanks for the thorough review. A few follow-ups:

Dead module commits (2e6fcda, 8ad174c): sp.js and spLedger.js were already deleted in earlier commits on this branch. Happy to reword those commit messages if you'd like — just let me know which ones.

Minor items: Will add encodeURIComponent(status) and wrap the /spurti/verify/:code route in a try/catch on the next pass.

Deployment note: Noted — will ping you when this merges so you can mark the date for the weekly panel.

@23f3001369

Copy link
Copy Markdown
Author

@sakshivk Follow-up on the minor items:

Both are already covered. Ready to merge whenever you are.

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.

3 participants