Home > OS >  Locking a document with firebase rules
Locking a document with firebase rules

Time:10-22

I have this set of rules in firestore

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
  
    match /{document} {
      allow read: if true;
      allow write: if isTrustworthy();
    }
    match /stores/{storeId}{
      match /{document} {
        allow read: if true;
        allow write: if isTrustworthy();
      }
      match /departures/{departureId} {
        allow read: if true;
        allow write: if !(resource.data.locked);
      }  
    }
        
    function isTrustworthy(){
      return isSignedIn() && emailVerified();
    }
    
    function isSignedIn() {
      return request.auth != null;
    }
    function emailVerified() {
      return request.auth.token.email_verified;
    }
  }
}

but I get an error when setting, updating or deleting in the departures path.

eg.

this.afs.collection('stores').doc(this.auth.store)
   .collection('departures')
   .doc(element.shipping.trackingNumber.toString())
   .set({"test":true})

the document is not existent, but it won't work neither if the document exist, independent of the parameter "locked" I got the following error:

ERROR Error: Uncaught (in promise): FirebaseError: [code=permission-denied]: Missing or insufficient permissions.

FirebaseError: Missing or insufficient permissions.

What I need is that if the document has a parameter "locked" set to true, it cannot be deleted, updated, nor set with {merge:true} option.

any help will be much appreciated.

CodePudding user response:

There are a few issues with your rule and code:

  1. On create, resource.data does not exist
  2. On update or delete, it could be that the field locked does not exist
  3. When you use set to create or update a document in Firestore, you have to add the options {merge: true}, to update the document instead of overwriting it in case it already exists.

Try this instead:

  • Code:

    this.afs.collection('stores').doc(this.auth.store)
      .collection('departures')
      .doc(element.shipping.trackingNumber.toString())
      .set({"test":true}, {merge: true})
    
  • Rules:

    match /departures/{departureId} {
      allow read: if true;
      allow create: if true;
      allow update, delete: if !resource.data.get("locked", false);
    } 
    

CodePudding user response:

I add a set of functions to my rules to make writing rules like this easier:

    function existing() {
        return resource.data;
    }
    function resulting() {
        return request.resource.data;
    }
    function matchField(fieldName) {
        return existing()[fieldName] == resulting()[fieldName];
    }

resource refers to the document being accessed, and request.resource refers to the new data for the document. With these you can refer to values on the existing or incoming document.

The matchField is useful in combination with various claims/Roles protect parts of the document(s) from being changed.

In your case you can check the status of a specific field "locked", for example some form of:

match /whatever/points/to/the/doc {
  allow write: if !existing().locked
}

(I use a couple dozen such functions MOSTLY to make my rules almost look like text)

  • Related