Summary
PUT /api/events/:eventId writes the entire request body straight into the Event document with no field whitelist, and is only gated by an "is this unit's coordinator" check — not by what fields are being changed.
Location
- Route:
backend/routes/events.js — router.put("/:eventId", isAuthenticated, isEventContact, eventsController.updateEvent)
- Controller:
backend/controllers/eventControllers.js, updateEvent (around line 215-233) — const updates = req.body; ... Event.findByIdAndUpdate(eventId, updates, { new: true, runValidators: true })
- Middleware:
backend/middlewares/isEventContact.js only verifies the caller's login email matches the organizing unit's contact email (i.e. that unit's CLUB_COORDINATOR account) — it says nothing about which fields on the event that contact is allowed to touch.
Impact
A CLUB_COORDINATOR — a mid-tier role, not an admin — can use this single endpoint to:
- Directly set
room_requests[].status to "Approved", bypassing the dedicated PRESIDENT-only approval route (PATCH /room-requests/:requestId/status, gated by authorizeRole("PRESIDENT")) sitting right above it in the same route file.
- Directly edit
budget.allocated / budget.spent on the event, bypassing the properly session-guarded, audited BudgetTransaction system entirely (no transaction record, no balance validation).
- Reassign the event to a different
organizing_unit_id.
- Inject arbitrary entries into
winners or participants.
Suggested fix
Whitelist the fields updateEvent is allowed to write (title, description, schedule, registration, media, etc.) and explicitly exclude budget, room_requests, organizing_unit_id, and winners — those already have (or should have) their own properly-authorized endpoints.
Summary
PUT /api/events/:eventIdwrites the entire request body straight into the Event document with no field whitelist, and is only gated by an "is this unit's coordinator" check — not by what fields are being changed.Location
backend/routes/events.js—router.put("/:eventId", isAuthenticated, isEventContact, eventsController.updateEvent)backend/controllers/eventControllers.js,updateEvent(around line 215-233) —const updates = req.body; ... Event.findByIdAndUpdate(eventId, updates, { new: true, runValidators: true })backend/middlewares/isEventContact.jsonly verifies the caller's login email matches the organizing unit's contact email (i.e. that unit's CLUB_COORDINATOR account) — it says nothing about which fields on the event that contact is allowed to touch.Impact
A CLUB_COORDINATOR — a mid-tier role, not an admin — can use this single endpoint to:
room_requests[].statusto"Approved", bypassing the dedicated PRESIDENT-only approval route (PATCH /room-requests/:requestId/status, gated byauthorizeRole("PRESIDENT")) sitting right above it in the same route file.budget.allocated/budget.spenton the event, bypassing the properly session-guarded, audited BudgetTransaction system entirely (no transaction record, no balance validation).organizing_unit_id.winnersorparticipants.Suggested fix
Whitelist the fields
updateEventis allowed to write (title, description, schedule, registration, media, etc.) and explicitly excludebudget,room_requests,organizing_unit_id, andwinners— those already have (or should have) their own properly-authorized endpoints.