diff --git a/backend/app/routers/events.py b/backend/app/routers/events.py index 140d046..e7bd57f 100644 --- a/backend/app/routers/events.py +++ b/backend/app/routers/events.py @@ -353,7 +353,9 @@ async def update_event( update_data["recurrence_rule"] = json.dumps({k: v for k, v in rule_obj.items() if v is not None}) if rule_obj else None # SEC-04: if calendar_id is being changed, verify the target belongs to the user - if "calendar_id" in update_data and update_data["calendar_id"] is not None: + # Only verify ownership when the calendar is actually changing — members submitting + # an unchanged calendar_id must not be rejected just because they aren't the owner. + if "calendar_id" in update_data and update_data["calendar_id"] is not None and update_data["calendar_id"] != event.calendar_id: await _verify_calendar_ownership(db, update_data["calendar_id"], current_user.id) # M-01: Block non-owners from moving events off shared calendars diff --git a/frontend/src/components/calendar/EventDetailPanel.tsx b/frontend/src/components/calendar/EventDetailPanel.tsx index f7ee3ff..0fe4b34 100644 --- a/frontend/src/components/calendar/EventDetailPanel.tsx +++ b/frontend/src/components/calendar/EventDetailPanel.tsx @@ -392,8 +392,8 @@ export default function EventDetailPanel({ // --- Handlers --- const handleEditStart = async () => { - // For shared events, acquire lock first (owners skip locking) - if (isSharedEvent && myPermission !== 'owner' && event && typeof event.id === 'number') { + // For shared events, acquire lock first + if (isSharedEvent && event && typeof event.id === 'number') { try { await acquireLock(); setLockInfo(null);