Skip to content

Make volunteer approval server-authoritative; unify identity resolution - #268

Merged
gregv merged 1 commit into
developfrom
fix/volunteer-app-staff-owned-fields
Aug 21, 2026
Merged

Make volunteer approval server-authoritative; unify identity resolution#268
gregv merged 1 commit into
developfrom
fix/volunteer-app-staff-owned-fields

Conversation

@gregv

@gregv gregv commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Problem

An approved mentor / judge / volunteer / sponsor who edited and re-submitted their application was silently un-approved — they vanished from approved lists, lost their check-in QR and mentor-panel access, and dropped out of attendance reporting.

Two causes:

  1. create_or_update_volunteer only preserved isSelected when the key was absent from the payload, and four of five frontend forms shipped isSelected: false from their initial form state (sponsor hardcoded it). Companion frontend PR removes those.
  2. Duplicate-doc variant: the read path resolved identity 3 ways (propel UUID → email → OAuth id) but the write path matched propel UUID only — so a user whose doc was stored under another identity shape saw their app, edited it, missed the write lookup, and fell into the CREATE branch, spawning a second pending doc and orphaning the approved one.

Changes

  • STAFF_OWNED_VOLUNTEER_FIELDS stripped from every self-service submit/update: isSelected, check-in fields, refund bookkeeping, certificates, sent_emails. Deposit payment fields are deliberately excluded — the hacker Stripe return sets those on /update. Admin flows are unaffected (update_volunteer_selection and update_hackathon_volunteers never route through this function). Also closes authenticated self-approval on the create path.
  • find_volunteer_by_caller_identity() — shared 3-way resolver now used by handle_get, create_or_update_volunteer, and mentors' _find_mentor_volunteer (3 copies → 1). Email step uses the verified PropelAuth email only, never the form payload.
  • Auth: all ten application submit/update routes flipped optional_userrequire_user; body user_id fallback removed. The web forms already require login (RequiredAuthProvider), so no user-facing change.
  • Test hygiene: ENVIRONMENT=test now suppresses the Slack/Resend notification fan-out — unit tests were posting real Slack messages and attempting real Resend sends.

Testing

  • 13/13 in api/volunteers/tests/ (was 11 passed + 1 pre-existing failure from a stale .update() assert — code calls .set(merge=True)).
  • Each new regression test verified to fail against the old code.
  • Full per-directory sweep matches the pre-change baseline exactly (certificates 3F / contact 1F / leaderboard 2F / messages 1F are pre-existing).

Deploy notes

  • No ordering constraint with the frontend PR: old FE + new BE → stripped; new FE + old BE → key absent → old guard preserves.
  • Follow-up worth doing: an audit script to find volunteers already clobbered by this bug (e.g. isSelected == false with checkInTime set is an impossible state under normal flow).

🤖 Generated with Claude Code

Fixes the bug where an approved mentor/judge/volunteer/sponsor editing
their application was silently reset to isSelected=false (and could lose
check-in / refund state the same way).

- STAFF_OWNED_VOLUNTEER_FIELDS stripped from every self-service
  submit/update in create_or_update_volunteer: isSelected, check-in
  fields, refund bookkeeping, certificates, sent_emails. Deposit payment
  fields (stripe_payment_intent_id, deposit_amount_cents,
  deposit_disposition) deliberately excluded — the hacker Stripe return
  sets those on /update. Also closes authenticated self-approval on the
  create path (payload could override the isSelected=False seed).
- find_volunteer_by_caller_identity(): shared 3-way resolver (propel
  UUID -> PropelAuth email -> OAuth user_id) now used by handle_get,
  create_or_update_volunteer, and mentors' _find_mentor_volunteer.
  Read and write matching the same docs stops edits from falling into
  the create branch and spawning duplicate isSelected=False docs.
- All ten /api/{type}/application/<event>/{submit,update} routes are
  @auth.require_user (were optional_user); identity comes from the
  verified token only — the body user_id fallback is gone.
- _notifications_disabled(): ENVIRONMENT=test suppresses the
  Slack/Resend fan-out — unit tests were posting real Slack messages
  and attempting real Resend sends.
- Regression tests for all of the above (each verified to fail against
  the old code); fixed test_update_volunteer's stale .update() assert
  (code calls .set(merge=True)).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gregv
gregv merged commit aeaa162 into develop Aug 21, 2026
6 checks passed
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