Firestore security rule to allow content sharing between 2 users? - firebase

I need to create a collection where the documents can only be viewed and edited by 2 users who both validated that the other is them partner.
My idea was to have a structure like this :
(Joe & Mary are the users uid)
users:
Joe:
partner: Mary
...
Mary:
partner: Joe
...
partners:
JoeMary:
Joe: true
mary: true
field: value
field: value
...
My first idea was to use the rule:
match /partners/{partnersId} {
allow read, update, delete: if request.auth != null && partnersId.contains(request.auth.uid);
allow create: if request.auth != null;
}
However it undelined with red arrows and someone told me that it is not working that way. No idea why...
Then I had this idea:
const useruid = request.auth.uid
const partner = get(/databases/$(database)/documents/users/$(request.auth.uid)).data.partner
allow read, update, delete: if get(/databases/$(database)/documents/partners/$(useruid+partneruid)).data.$(request.auth.uid) == true;
allow read, update, delete: if get(/databases/$(database)/documents/partners/$(partneruid+useruid)).data.$(request.auth.uid) == true;
This is a pretty complicated rule with 3 reads that have some cost. And of course there is red evrywhere...
Is there a better option for that ?
Thanks

There are many ways to ago about this. It's hard to know what exactly will work for you, since your requirements are a bit vague.
One way is to simply put both of the user's UID strings into a single array field in the document. Then write a rule to make sure the current user's UID is in that array:
match /partners/{partnersId} {
allow read, update, delete: if resource.data.partners.hasAny([request.auth.uid]);
allow create: if request.auth != null;
}
The requirement here is that the document must contain an array field called "partners" with the strings for the collaborating UIDs. The document ID doesn't matter. It could just be a random ID.

Related

How to add a Firebase security rule for checking if logged in users email is in a list

I try to add a security rule that should grant write access given that the logged in user is in a list within the record.
I.e. my "tournament" record has a List officials in where I put the emails of those that should be able to alter the tournament.
I don't get this to work so I guess that my rule has some error in it.
match /tournaments {
allow read: if request.auth.uid != null;
allow write: if request.auth.token.email in resource.data['officials'];
}
The rule you have written is has some missing syntax. The rule must be like mentioned below:
match /tournaments/{tournamentID} {
allow read: if request.auth.uid != null;
allow write: if request.auth.token.email in resource.data['officials'];
}
The /tournaments/{tournamentID} indicates that the rule will apply for all documents present in tournament collection and {doc_id} is a wild card to representing documents in that collection.

Is it possible to create firestore rules based on specific fields?

I'm going to try to generalise a bit here to make the question simpler. I have a Firestore database that stores users and forum posts. I'm trying to write the rules for the forum posts specifically.
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null
}
function isOwner(userID) {
return request.auth.uid == userID
}
// ...
match /forum/{topicID} {
allow read: if true
allow create: if isSignedIn()
allow update, delete: if isSignedIn()
}
}
So here, I've got it so that you can create, update and delete if you are signed in. I also need to add the check, "Do they own this post?", with the isOwner() function.
That's all chill, but my problem is - each forum post (AKA topic) has two fields in them that can be updated by anybody who is signed in. These fields are likes and likeCount.
likes is an array of strings, each string is a user's id. likeCount is a number equal to the likes array length.
I'm starting to see that this would have been easier if likes was a sub-collection and likeCount could just be likesSnapshot.docs.length. I just fear the amount of recoding that might involve!
So, long story short, I want to do something along the lines of:
match /forum/{topicID} {
allow read: if true
allow create: if isSignedIn()
allow update, delete: if isSignedIn() && isOwner(resource.data.user.id)
match /likes && /likeCount {
allow create, update: if isSignedIn()
allow delete: if isSignedIn() && isOwner([THE USER ID THAT IS BEING REMOVED FROM THE ARRAY])
}
}
...but I'm not sure of the best way to go about it!
Pleases and thank yous in advance :)
It's not possible to target specific fields like you are showing in security rules. You can only match whole documents with a match statement.
If you want make sure certain users can only modify certain fields, you can use the MapDiff api to check that only those fields are being changed in the document data. It will go something like this:
if isSignedIn() &&
request.resource.data.diff(resource.data).affectedKeys().hasOnly(["likes", "likeCount"])
This will evaluate true if only likes and likesCount are being modified, and the user is signed in.
Yes you can do this. I actually did it recently in my own forum.
The way to do it is to allow update if isOwner and restrict what non owners are able to update. You can do that like this:
allow update: if request.auth.uid != null
&& request.resource.data.{some field} == resource.data.{some field}
&& request.resource.data.title.{some other field} == resource.data.{some other field}
Put all data fields that you don't want non owners to be able to update in the {some field} placeholder. The rule is checking that certain fields have not changed and thus restricting what a user is allowed to change in a document.
Then allow owners full update privs:
allow update: if isOwner()

Firestore rules based on path component and other collection

I am trying to establish a rules, where user would only be able to perform operations on Chat messages after they have made a purchase of one of the services.
My database structure looks like this:
I have purchases collection: purchases/{purchaseId} which contains buyer_id field.
I also have messages collection: /channels/{purchaseId}/thread/{threadId}.
I want to allow CRUD operations if the users in thread are the same as buyer_id from purchases collection, in purchaseId document.
This is what I've tried, using current user's ID (auth) for now. This doesn't work, either.
Ideally, I would substitute request.auth.uid with the field from the document I am trying to access, but this would do for now.
match /channels/{purchaseId}/thread/{threadId} {
allow read, create, update, delete: if get(/databases/{database}/documents/purchases/{purchaseId}).data.buyer_id == request.auth.uid;
}
I am getting the standard permissions error. What am I missing?
You syntax is wrong when defining the path. Try this:
match /channels/{purchaseId}/thread/{threadId} {
allow read, write: if get(/databases/$(database)/documents/purchases/$(purchaseId)).data.buyer_id == request.auth.uid;
}
Substituting request.auth.uid with the field from the document:
match /channels/{purchaseId}/thread/{threadId} {
allow read, write: if get(/databases/$(database)/documents/purchases/$(purchaseId)).data.buyer_id == resource.data.buyer_id;
}

Access userId without having it as a field

I'm writing Firestore security rules for my project. I want to allow users to edit information in their own user page, but not in anyone else's. Right now I don't save userId as a field in each user, only as the reference to the user document. I know how to access fields in each user, but not the reference to them. See picture:
match /Users/{document} {
allow update: if request.auth.uid == userId; //how do I reach the userId without having it as a field
}
I do not want to add userId as a field in each user, there must be an easy way of accessing the path.
As mentioned in the Firestore docs you get the document id from the match query.
In your case this would be document from match /Users/{document}. You could also rename this query to match /Users/{userId} to make it work.
Check the second example in the documentation on using authentication information in security rules:
Another common pattern is to make sure users can only read and write their own data:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
So in your case that'd be if request.auth.uid == document.

Cloud Firestore Security Rules - only allow write to specific key in document

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?
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.
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.

Resources