skip to Main Content

I am trying to create a document in Google Firestore as follows:

    const docData = {
      name: name,
      type: type,
      ownerId: this.auth.session!.userUid
    }

    await addDoc(collection(this.firebase.firestore, "projects"), docData)

I can see (via an onAuthStateChanged(...) callback), that the user is signed-in and making an authenticated request. The payload is { name: "dfgdfgdfg", type: "CommonV1", ownerId: "5sp...F53" } (ellipses added for brevity; the full ownerId is given in the actual request). However, I receive an "FirebaseError: Missing or insufficient permissions." error.

I would like projects to be accessible to the user who created them, and nobody else (to begin with). To this end, my security rules are as follows:

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {

    // True if the user is signed in and they own the resource.
    function OwnsResource() {
      return request.auth.uid != null && resource.data.ownerId == request.auth.uid;
    }

    // By default, no documents may be read.
    match /{document=**} {
      allow read, write: if false;
    }
    
    // Allow a project to be created, read or written by its owner.
    match /projects/{project} {
      allow create, read, write: if ownsResource();
    }
  }
}

I believe the problem is in my rules, but I cannot see where.

How can I resolve this problem and create the project, as desired?

2

Answers


  1. Chosen as BEST ANSWER

    I was pointed to the firebase rules editor in the Firebase console. It helped me identify an error (in addition to Phil's comment). Where I had:

        function OwnsResource() {
          return request.auth.uid != null && resource.data.ownerId == request.auth.uid;
        }
    

    It should have had:

        function ownsResource() {
          return request.auth.uid != null && request.resource.data.ownerId == request.auth.uid;
        }
    

    This works. For anyone reading this: the Firestore rules playground in the Firebase console is an excellent tool.


  2. Ignoring that allow create is redundant with allow write: resource.data points to the data in your document as it was when you made the change. So for a document that does not yet exist, this will be null. You then call null.ownerId, which throws an exception and blocks the change.

    To prevent throwing the exception, you’d need to check for this first:

    function OwnsResource() {
      return request.auth != null && resource.data != null && resource.data.ownerId == request.auth.uid;
    }
    

    However, this rule won’t handle a newly created document (as resource.data will be null), you’d need to either add to the above function, or create a new one:

    function OwnsCreatedResource() {
      return request.auth != null && resource.data == null && request.resource.data.ownerId == request.auth.uid;
    }
    

    It is important to note that in the above rules that the prior state of the document is checked. By using just request.resource.data.ownerId over resource.data.ownerId, the new write can change the old ownerId of the document. So userB could take over userA‘s project.

    Additionally, note that request.auth.uid != null has been corrected to request.auth != null. The former statement will throw an exception when a user is not logged in.

    This then allows your rules to become:

    // Allow a project to be created, read or written by its owner.
    match /projects/{project} {
      allow read, write: if ownsResource() || ownsCreatedResource();
    }
    

    or

    // Allow a project to be created, read or written by its owner.
    match /projects/{project} {
      allow read, update, delete: if ownsResource();
      allow create: if ownsCreatedResource();
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search