Reputation: 696
I'm currently writing some rules for my app with a Firestore database. Currently everyone can read data and authenticated users can write.
match /quizzes/{quizId} {
allow read;
allow write: if request.auth != null;
}
That works fine, but I also want unauthenticated users to write only to a specific key in a document.
Example content of a document:
{
title: 'title',
plays: 12,
playedBy: [//Filled with user id's],
...
}
Is there any way that limits unauthenticated users to only have write access to the playedBy array and not the other keys of that document?
Upvotes: 4
Views: 2460
Reputation: 18076
After the earlier answer (late 2019, I believe), Firebase has brought in Map.diff.
Something like:
match /quizzes/{quizId} {
allow read;
allow write: if request.auth != null ||
request.resource.data.diff(resource.data).affectedKeys() == ["playedBy"].toSet()
}
Tested code where I use it can be seen here.
Upvotes: 6
Reputation: 600131
Sure thing. But it may become a bit involved if you have a lot of fields.
Let's start with the simplest example. Something like this allows an unauthenticated user to write the playedBy
as long as that is the only field in the document:
if request.auth != null || request.resource.data.keys().hasOnly(['playedBy'])
This works if the unauthenticated user is creating a new document, or updating an existing one. But it will stop as soon as the document contains more fields, since request.resource.data
contains all fields the document will have after the write succeeds.
So the better alternative is to check that only the playedBy
is modified, and that all other fields have the same value as before. The tricky bit there is handling the non-existence of fields, which I typically handle with a few helper functions:
function isUnmodified(key) {
return request.resource.data[key] == resource.data[key]
}
function isNotExisting(key) {
return !(key in request.resource.data) && (!exists(resource) || !(key in resource.data));
}
And then:
if request.auth != null &&
request.resource.data.keys().hasOnly(['title', 'plays', 'playedBy']) &&
isUnmodified('title') &&
isUnmodified('plays')
The exact rule might be a bit off, but I hope this is enough to allow you to complete it yourself.
Upvotes: 8