Skip to content

UC-3 Student checks in at an event via QR

The core loop. Everything else exists to make this one tap trustworthy.

Primary actor Student
Supporting actors Event Attendant (displays the code)
Priority Must
Satisfies FR-D1, FR-D2, FR-D4, FR-D5, FR-D7, FR-E1
Precondition The student is signed in, a current student, and enrolled; the task's event is running.
Trigger The student taps "Scan" and captures the event QR, or types the event's 7-character backup code.

Main success scenario

  1. The attendant displays the event QR (a signed token, re-minted every 30 seconds) on a projector via the display link or the live-event screen.
  2. The student scans it in the app.
  3. The app posts the token with the session cookie.
  4. The system verifies the signature, expiry and challenge binding, then records a check-in with method = event_qr, one per student per week.
  5. The week flips to complete, the countdown updates, and a post-check-in tip is shown (UC-6).

Extensions

  • 4a. Duplicate scan. 409, "already checked in this week". Nothing is double-counted.
  • 4b. Expired code. 400 asking for a rescan of the current code. A slow scan is not a forgery.
  • 4c. Invalid, tampered, or another challenge's code. 400, invalid token.
  • 4d. Not a current student. 403 before any check-in logic runs.
  • No camera or bad lighting. The attendant reads out the backup code; it is accepted through the same path.
  • Legacy self-service check-in. Retired. A student's own check-in requires the live event code; staff can verify a student directly (UC-4) or override with an audit row (UC-11).

Acceptance criteria (Gherkin)

Feature file Test module
qr_event_binding.feature test_qr_event_binding_bdd.py
rotating_qr_token.feature test_rotating_qr_token_bdd.py
scanned_qr_only_checkin.feature and frontend/scanned_qr_only_checkin.feature test_scanned_qr_only_checkin_bdd.py
event_backup_code.feature test_event_backup_code_bdd.py
week_sheet_checkin_cta.feature test_week_sheet_checkin_cta_bdd.py

Plain pytest: test_qr_checkin.py, test_rotating_qr_token.py, test_event_backup_code.py, test_task_qr_ttl.py. Frontend: QrScanner.test.tsx.

Views

Code: backend/app/services/qr.py, backend/app/services/checkins.py, backend/app/routers/passport.py, frontend/src/components/Passport/QrScanner.tsx. Operational note: rotating WP_QR_SECRET invalidates every live code; the runbook has the procedure.