Firestore Real-time Listener Conflicts with Security Rules in React Project

19 Views Asked by At

Overview:

I am facing an issue with the real-time listeners in my React project, where it conflicts with security rules in Firestore. Specifically, security rules checks for listeners are triggered before a new document is created, leading to denied permissions.

It is a race condition with the following events:

  1. Firebase sdk initiates creation of new document
  2. App is instantly aware of new document through real time listener
  3. App creates a new listener for a subcollection of said document
  4. Firestore rules engine need to check a field of said document to grant access
  5. Since document doesn't exist yet in firestore backend, permissions are denied
  6. Event 1 finishes: document is created in firestore backend (too late)

Details:

In my project, I have a Firestore collection called adventures and a sub-collection called layers. I use the "firebase/firestore" library to create a new adventure in Firestore, which in turn triggers a real-time update of my listeners.

Here's how I create a new adventure in my React project:

import {addDoc, collection} from "firebase/firestore";
[...]
const adventuresRef = collection(firestore,"adventures")
const adventureId = (await addDoc(adventuresRef, newAdventure)).id;  

Here is the listener for the user adventures:

import { useFirestoreCollectionData } from "reactfire";  
[...]
const firestoreQuery = query(adventuresRef, where("userId", "==", userId), orderBy("createdAt"));  
const { data: userAdventures } = useFirestoreCollectionData(firestoreQuery, {  
  idField: "id",  
});

Additionally, I have listeners for listing the layers of each adventure.

The user adventures listener updates before the new adventure finishes being created in the backend (which is expected for real-time). However, my security rule for the layers compares the authenticated user ID against the adventure's userId and only allows reading if they match. Due to the real-time update, the security validation happening on the Firestore backend triggers before the adventure is created, causing the validation to fail.

Details of my security rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    [...]
    function getAdventure(adventureId) {
      return get(/databases/$(database)/documents/adventures/$(adventureId)).data
    }
    function getIsOwnerOrIsPublic(adventureId) {
      let adventure = getAdventure(adventureId);
      let isOwner = adventure.userId == request.auth.uid;
      let isPublic = adventure.isPublic == true;
      return isOwner || isPublic
    }
    function getCanRead(adventureId) {
      return getIsOwnerOrIsPublic(adventureId) || [...]
    }
    [...]
    match /adventures/{adventureId} {
      [...]
      match /layers/{layerId} {
        allow read: if getCanRead(adventureId);
        [...]
      }
      [...]
    }
  }
}

Current Workaround

As a temporary workaround, I create the adventure through a Node.js backend, not leveraging real-time updates. I am looking for suggestions on how to address this issue better.

0

There are 0 best solutions below