Sonu Sharma
Sonu Sharma

Reputation: 334

security rules to allow update of specific fields

I am new to security rules. I have to write security rule to prevent a user to update a document except one field.

lets say i have a doc

{ field1 : one, field2 : two, field3 : three, . . . fieldn : n }

the user logged in should be able to update only field2. using firestore security rules.

Upvotes: 10

Views: 3625

Answers (2)

ObjectJosh
ObjectJosh

Reputation: 641

If you prefer the method of specifying which fields can be editable, I found a section in this tutorial to be helpful: Intermediate topics in Firebase Security Rules - Firecasts

// Make sure the request has only the specified allowed fields    
function editingOnlyAllowedFields(allowedFields) {
    let editedKeys = request.resource.data.diff(resource.data).affectedKeys();
    return editedKeys.hasOnly(allowedFields);
}
// Then, you can specify which fields you want to be updatable
allow update: editingOnlyAllowedFields(["field1", "field2"]);

Upvotes: 6

Frank van Puffelen
Frank van Puffelen

Reputation: 598817

There is no explicit way in security rules to validate the update that is happening. But what you can do is validate the data in the document before and after the write operation. By comparing those two, and by knowing what fields the document can contain, you can ensure that only specific fields can be updated.

I often use this little helper function in my security rules:

function isUnmodified(key) {
  return request.resource.data[key] == resource.data[key]
}

As its name implies, it ensures that a certain key/field is not modified in this write request. For example, this rule then only allows a user to update their profile document, as long as they don't modify the name field (unless they're an admin):

allow update: if isAdmin(request) || 
  (request.auth.uid == uid && isUnmodified(request, resource, 'name'));

I also have this helper function, which checks whether a specific field exists:

function isNotExisting(key) {
  return !(key in request.resource.data) && (!exists(resource) || !(key in resource.data));
}

This is important, because sometimes you want to allow a field to be only written once, or only allow it to be updated if it already exists. Sometimes I use isNotExisting for that, but I find myself more these days using the more granular actions (create, update) over the aggregate write rule.

Finally, you can require certain fields, as in this creation rule:

  allow create: if request.auth.uid == uid &&
    request.resource.data.keys().hasOnly(['lastIndex', 'lastUpdated']) &&
    request.resource.data.keys().hasAll(['lastIndex', 'lastUpdated']) 

So a user can only create a profile document if they specify lastIndex and lastUpdated fields. If they specify any additional fields, or specify fewer fields, the creation will be rejected.

Now with this knowledge, we can go back to your requirement, and see how to implement it. As said before, you will need to make a statement on each individual field, without having a wildcard in there. So if your document has three fields (field1, field2, and field3), which must all exist, and the user can only update field2, that'd be something like:

allow update: if request.resource.data.keys().hasAll(['field1', 'field2', 'field2']) &&
  isUnmodified('field1')) && isUnmodified('field3'));

There may nowadays be a shorter way to do this by using the set and map diff operations that are shown here: https://firebase.google.com/support/release-notes/security-rules#february_13_2020 like:

// This rule only allows updates where "a" is the only field affected
allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(["a"]);

Upvotes: 33

Related Questions