Fix two shared calendar bugs: lock banner missing and calendar not found on save

Bug 1 (lock banner): Owners bypassed lock acquisition entirely, so no DB lock
was created when an owner edited a shared event. Members polling GET
/shared-calendars/events/{id}/lock correctly saw `locked: false`. Fix: remove
the `myPermission !== 'owner'` guard in handleEditStart so owners also acquire
a temporary 5-min edit lock when editing shared events, making the banner
visible to all other members.

Bug 2 (calendar not found on save): PUT /events/{id} called
_verify_calendar_ownership whenever calendar_id appeared in the payload, even
when it was unchanged. For shared-calendar members this always 404'd because
they don't own the calendar. Fix: add `update_data["calendar_id"] !=
event.calendar_id` to the guard — ownership is only verified when the calendar
is actually being changed (existing M-01 guard handles the move-off-shared case).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-06 17:16:35 +08:00
parent 38334b77a3
commit c55af91c60
2 changed files with 5 additions and 3 deletions

View File

@ -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

View File

@ -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);