fix(rules): allow completedByUsers on session update so finished games close (B-001 P1)
The sessions allow-update rule required affectedKeys().hasOnly(['status','completedAt']), but the async-game completion path (markUserComplete) always writes completedByUsers, so every 'I reached results' write was denied and the session stayed active forever -> the couple was locked out of starting any new game (only the destructive 'End their game' worked, since abandonSession only diffs status/completedAt). Rule now permits ['status','completedAt','completedByUsers'], lets any couple member record completion progress, keeps startedByUserId immutable and status monotonic (active->completed). Deployed + verified live: both finish a game -> session auto-completes (completedByUsers =[both]) -> next game starts immediately (no 'Waiting for partner' block). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ebd3b2ed1f
commit
a82c43ad90
|
|
@ -309,17 +309,20 @@ service cloud.firestore {
|
||||||
allow create: if isCouplesMember(coupleId)
|
allow create: if isCouplesMember(coupleId)
|
||||||
&& request.resource.data.startedByUserId == request.auth.uid;
|
&& request.resource.data.startedByUserId == request.auth.uid;
|
||||||
|
|
||||||
// Update: only the user who started the session can update it, OR valid status transitions.
|
// Update: any couple member may record session progress/completion.
|
||||||
// startedByUserId is immutable for direct client writes.
|
// (Async two-device games mark each player done via `completedByUsers`; the
|
||||||
|
// session flips active→completed once both are in. The previous rule only
|
||||||
|
// allowed `status`/`completedAt`, so every `completedByUsers` write was denied
|
||||||
|
// and finished games never closed — locking the couple out of new games. B-001.)
|
||||||
allow update: if isCouplesMember(coupleId)
|
allow update: if isCouplesMember(coupleId)
|
||||||
// Either the original starter can update
|
// startedByUserId is immutable for direct client writes.
|
||||||
&& (resource.data.startedByUserId == request.auth.uid
|
|
||||||
// Or status transition is valid: active → completed
|
|
||||||
|| (resource.data.status == 'active' && request.resource.data.status == 'completed'))
|
|
||||||
// startedByUserId cannot be changed by clients
|
|
||||||
&& request.resource.data.startedByUserId == resource.data.startedByUserId
|
&& request.resource.data.startedByUserId == resource.data.startedByUserId
|
||||||
// Only a fixed set of fields may change
|
// Only session progress/completion fields may change.
|
||||||
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['status', 'completedAt']);
|
&& request.resource.data.diff(resource.data).affectedKeys()
|
||||||
|
.hasOnly(['status', 'completedAt', 'completedByUsers'])
|
||||||
|
// status is monotonic: stay the same, or transition active → completed (never revert).
|
||||||
|
&& (request.resource.data.status == resource.data.status
|
||||||
|
|| (resource.data.status == 'active' && request.resource.data.status == 'completed'));
|
||||||
|
|
||||||
// Delete: server-only (admin SDK). Admin SDK bypasses rules.
|
// Delete: server-only (admin SDK). Admin SDK bypasses rules.
|
||||||
allow delete: if false;
|
allow delete: if false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue