feat(roster): gate joins on registered student-ID list
Adds an optional roster.json (set of allowed student IDs) loaded at startup. add_participant() raises StudentIdNotInRoster when the gate is on and the supplied id is not present; route returns 403 with a clear message and logs a roster_reject audit event. Names are NOT checked against the roster: the join form asks for a current name as a soft deterrent, but the only hard check is the id. Includes a deploy/build_roster.py helper that turns class_register attendance.xlsx into roster.json. Bootstrap env file now exports QUIZ_ROSTER_PATH; missing file disables the gate (legacy behaviour). Also drops the user-facing "The cookie is per-device." line from the join card — students don't need to know the implementation; replaced with "Enter your registered student ID and your current full name."
This commit is contained in:
@@ -12,7 +12,7 @@ from app import auth
|
||||
from app.config import Settings
|
||||
from app.models import JoinRequest, StudentEventRequest
|
||||
from app.rate_limit import client_ip
|
||||
from app.room import DuplicateStudentId, RoomManager
|
||||
from app.room import DuplicateStudentId, RoomManager, StudentIdNotInRoster
|
||||
|
||||
|
||||
def router(settings: Settings, rooms: RoomManager) -> APIRouter:
|
||||
@@ -63,6 +63,27 @@ def router(settings: Settings, rooms: RoomManager) -> APIRouter:
|
||||
cookie_id = str(uuid4())
|
||||
try:
|
||||
await rooms.add_participant(sid, student_id, name, cookie_id)
|
||||
except StudentIdNotInRoster:
|
||||
# Roster gate: id is not on the registered class list. Log a
|
||||
# `roster_reject` event with attempted ip/ua/name so the
|
||||
# instructor sees casual fishing attempts in the audit log.
|
||||
await rooms.log_event(
|
||||
sid,
|
||||
student_id=student_id,
|
||||
kind="roster_reject",
|
||||
detail={
|
||||
"attempted_name": name,
|
||||
"ip": client_ip(request),
|
||||
"ua": (request.headers.get("user-agent") or "")[:200],
|
||||
},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=(
|
||||
"This student ID is not on the class list. "
|
||||
"Check the digits, then ask the instructor if it still fails."
|
||||
),
|
||||
) from None
|
||||
except DuplicateStudentId:
|
||||
# First-claim-wins anti-hijack: a participant row already
|
||||
# exists for this student_id. Could be a hijack attempt
|
||||
|
||||
Reference in New Issue
Block a user