firestore security rules get value of dynamic key?

Viewed 710

document structure:

{
    [id 1] {
        field1: "value",
        field2: "value",
        field3: "value"
    }
    [id 2] {
        field1: "value",
        field2: "value",
        field3: "value"
    }
    [id 3] {
        field1: "value",
        field2: "value",
        field3: "value"
    }
}

the map ids reference documents and cannot be hardcoded, but field names (field1, field2...), are the same in each map

is there a way to check that only 1 field in any 1 map is changed, and allow write if those conditions are met?

2 Answers

One trick you can use in order to keep all the validation logic within Firestore, is adding redundancy to your write requests. For example, you can send the following data from the client:

firestore.collection('A').doc('B').update({ 
  'your_id_here': { 
    field1: 'my new value',

    // keep old data, since we are updating a nested object
    field2: 'original value', 
    field3: 'original value'
  },
  
  // this is the redundancy component
  meta: { id: 'your_id_here' }
});

Then, in your Firestore rules you can define your write rule as follows:

function valid() {

  // grab ID from "redundant" part of the request
  let metaID = request.resource.data.meta.id;

  // grab changed keys in the root of the document
  let rootAftectedKeys = request.resource.data.diff(resource.data).affectedKeys(); 

  // validate root keys; need to include "meta", since it is now also part of the object
  let rootChangesGood = rootAffectedKeys.hasOnly([metaID, 'meta']);
  
  // grab the nested object in question             
  let nestedObjectChanges = request.resource.data[metaID].diff(resource.data[metaID]).affectedKeys();

  // check there was only one affected key
  let nestedObjectChangesGood = nestedObjectChanges.size() == 1;

  return rootChangesGood && nestedObjectChangesGood;
}
allow write: if valid();

As you can see, I am using metaID variable to dynamically access the key of the object.

The advantage of this approach is that (1) all validation logic is within Firebase and (2) it will be much faster than executing a cloud function (since you are only communicating with Firestore directly).

The downside of this approach, however, is that you will have this meta object floating around for everyone to see. On top of that, I would add more validations to ensure that meta object itself has only required data in the right format. I highly recommend thoroughly testing all possible scenarios.

Security rules are not flexible enough to do what you're asking. This requires looping and assignment, both of which are not available in the language. I suggest instead using a backend to collect the data from the client, then check to see if it's valid.

Related