I'm trying to create a Firestore auth rule that checks the current user against a list of team members in a team document. The members are stored as document references so I've been trying things like this:
match /teams/{document=**} {
allow read: if path("/users/" + request.auth.uid) in resource.data.members;
}
But when I try and access the team document I get told there is an Auth failure.
Each team member has their own document in /users using their UID as a key. So a user might be /users/12345678 and the teams document might have:
/teams/team1 {
members: [/users/12345678, ....]
}
Where the members are Reference types.
So far I've not been able to figure this out as Firestore does not seem to have the concept of a document reference type in it's auth rules.
Any suggestions?
Security rules do have a concept of a reference, and it's represented as a Path type object. When a document reference is read by security rules, you have to treat it like a Path. And that Path will be fully qualified like this:
/databases/$(database)/documents/collection/documentId
Where $(database) comes from your usual top-level database wildcard match.
So, your rule might be implemented like this:
match /teams/{document=**} {
allow read: if /databases/$(database)/documents/collection/users/$(request.auth.uid) in resource.data.members;
}
Note that in security rules, you can build a path simply by starting with a /, and use $(foo) for interpolating variables as path components.
Related
I can't figure out how to write a Firestore rule that reflects: "when the user requests a doc from the messages collection, check that the doc above that "messages" collection includes a field thats an array which includes their uid. I've gotten the rule to work on the top level but if I try to access a document inside a collection, which is inside the "chats" collection, the user is denied.
Here's my relevant rule:
match /chats/{chatId}/{allChildren=**} {
allow read, update: if request.auth.uid in resource.data.usersUids;
}
This works (the chatId doc has an array of uids):
await db.collection("chats").doc(chatId).get()
This does not work (none of the messages have an array of uids):
await db.collection("chats").doc(chatId).collection("messages").get()
It seems like the heart of the issue is that I'm trying to get the resource.data of the messages collection rather than the resource of the chatId doc, but I can't figure out to solve this.
resource only contains the data from the specific document that was matched by the entire path. It doesn't contain anything from any parent documents that appear in that path.
Any time you want to use fields from a document that wasn't matched by the full path, you have to get() the document using its own full path as described in the documentation. For example:
match /chats/{chatId}/{allChildren=**} {
allow read, update: if request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.usersUids;
}
I have a simple fireStore collection named jokes where there is a document per joke.
In a joke, there is a key published: boolean
So the idea is to have a single collection with jokes, but each document can be either published or unpublished. I would not like users to view unpublished jokes.
In my fireStore rules, i have the following:
match /jokes/{id} {
allow read: if get(/databases/$(database)/documents/jokes/$(id)).data.published == true
}
In my application, I want users to be able to see only the published jokes, therefore i use the where
this.$fireStore.collection('jokes').where('published', '==', true).get()
When I do this, the console tells me i got insufficient permissions.
Is it possible to use this pattern, or do I have to use cloud functions to serve the published jokes? Or maybe a separate collection for unpublished ones?
You don't need a get() in the rule for this, as the current document is already available as resource. In fact, that get() is the problem here, as the rules engine cannot statically evaluate that for all document in one go.
match /jokes/{id} {
allow read: if resource.data.published == true
}
Also see the documentation on securely querying data, specifically the section on securing and querying documents based on a field
When submitting data, I want to ensure that my client cannot persist random fields.
Meanwhile, I want to keep my app the simplest as possible and I am trying to do it using only firestore rules and/or indexes (i.e. not using some server side express). Is it possible?
I know how to check the existence and the type of a field :
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /example/{exampleId} {
allow create: if "fieldOK" in request.resource.data &&
request.resource.data.fieldOK is string
}
}
}
But I do not know how to block the creation of a random field such as "fieldBS" when you cannot do a loop in your rules.
Yes, you can ensure that an object has only a certain range of keys using hasAll or hasOnly.
request.resource.data.keys().hasAll(['admin']);
request.resource.data.keys().hasOnly(['admin']);
You can use this at arbitrary levels of nesting on Map (key/value pair) style data.
The Firebase Rules Reference is a great resource for questions like this.
After checking only the supported range of keys are present, it's still important to sanity check each individual field thereafter.
I know that Firebase has the FieldValue class, which can be used to generate e.g. a server-side timestamp when writing a document (link).
What's the preferred practice for inserting the current user's uid into a document?
Having the client provide that field seems to allow misuse - unless I provide a server rule that checks for (new/updated) documents to match the request.auth.uid, something like:
service cloud.firestore {
match /databases/{database}/documents {
match /broadcasts/{broadcast}/chatMessagesCollection/{message} {
allow write: if request.resource.data.uid == request.auth.uid;
allow read: if true;
}
}
}
I can't find anything on the web for the use-case of having a document be populated with the user writing it -- so what's the best take on this?
What you're doing now with security rules to enforce that the provided UID matches the current user is exactly the right thing to do. There is really nothing better (for this specific use case), and this is a common practice.
I've even written about it in this blog series: https://medium.com/firebase-developers/patterns-for-security-with-firebase-per-user-permissions-for-cloud-firestore-be67ee8edc4a
On my app I am trying to make it so that users have to have a unique username.
My current method is to have a Social Collection with one document called Usernames. That document will store the users userID as the key for the field and then their username for the value.
I am struggling to write the correct security rules for this. I would like it so that:
All signed-in users can get this document
Users can only update their own data in the document, formatted as [theirUserId: theirUsername]
There can be no duplicate usernames, e.g.
userIdA: "foo"
userIdB: "foo"
At the moment the only point that I can't get to work is checking to see whether a username is already taken.
Another solution I have thought of is to reverse the fields (username: userID). But I can't figure out a way how to write the security rules for this method either.
Current Rules
// Usernames
match /Social/Usernames {
allow get: if request.auth.uid != null;
allow update: if isUserNameAvailable();
}
// Functions
function isUserNameAvailable() {
// This line works
return (request.writeFields.hasOnly([request.auth.uid]))
// This one doesn't
&& !(resource.data.values().hasAny([request.writeFields[request.auth.uid]]));
}
Firestore Data Structure
Any help is greatly appreciated, many thanks!