Firestore Security Rules for this scenario - google-cloud-firestore

I am new to the Firestore security rules and I wanted to make sure that the rules I wrote are secure for my case.
My database structure is like the following:
users / userId / employees / employeeId / files / fileId
The reason I don't denormalize it and create a separate collection for users, employees and files is because this application does not require any sort of cross collection query, there is no place in the application where all employees or their files need to be listed. Which brings me to the rules.
Only the owner of the employee or file collections should be able to access it. Everything inside employees or files can be changed. For the users collection, only creation should be allowed since new users should be able to be created when signing in but no user should be able to edit or delete any other existing user.
Apart from all of the above, there isn't anything else, there are no roles for this app.
My rules are the following:
service cloud.firestore {
match /databases/{database}/documents {
//can read and create if matching userId
//CREATE: NO USER CAN DELETE ACCOUNTS
match /users/{userId} {
allow read, create: if request.auth.uid == userId;
//can read and write if matching userId
match /employees/{employeeId} {
allow read, write, update: if request.auth.uid == userId;
//can read and write if matching userId
match /files/{fileId} {
allow read, write: if request.auth.uid == userId;
}
}
}
}
}
My question would be if my rules are secure? This seems awfully simplistic and I am just not sure if it is enough for my case.
Thanks in advance!

Your rules look fine to me.
But instead of taking my word for it, I strongly suggest using the Firestore local rules emulator and write some tests against your rules in order to verify that very specific queries (that you define) will be allowed or denied. Run these tests against your rules every time your rules have to change, for whatever reason, so you have confidence that the rules are still going to work as expected. I guarantee you this procedure will yield better and faster results than posting to Stack Overflow every time you're wondering if a set of rules will do what you expect. :-)

Related

How do you know if your firebase security rules are good enough so you can't get hacked?

I am brand new to firebase and i'm not sure if my firebase security rules are good enough so no one can hack into my database and change whatever they want. How do you know if your firebase security rules are any good?
I really do not want to allow anyone to change any data unless they are the current user changing only their personal data.
Here is a picture of my current firebase security rules:
How do I know if my firebase security rules are any good?
Also not sure if this will be useful but I only have 2 collections in my firestore data. The first one is "users" that just has basic information about my users (name, email address, etc). The second is "posts" that just has basic info about a post (likes, comments, etc)
How do you know if your firebase security rules are good enough so you can't get hacked?
That is really unanswerable. It's like asking "How do I know I'm the smartest person in the world"; you can't know that without competing with every person in the world in a battle of the brains.
Your security rules are a bit like that, except that you're not competing with everyone else but only with folks trying to access your data in a malicious way. And instead of an undefined battle of the brains, you're pitching your security rules (which they can't see) against their skills of deduction and familiarity with the API.
Good security rules allow exactly what your code also does, and nothing else.
A good example of this is your first create rule:
match /users/{userID} {
allow create: if true;
}
So this rule allows anyone in the world to create a document under any ID that they want in your database, simply by calling firebase.firestore().document("users/IAMTREV347").set({ whateverKeyIWant: "WithWhateverValue" }).
Your own code is probably be a bit more restrained than that. For example, it seems that you want to store documents in /users/$uid, so under the UID of the current user in your app. In code that might be something like this:
const uid = firebase.auth().currentUser.uid;
firebase.firestore().collection("users").doc(uid).set({
uid: uid,
name: "Trev345"
})
So you'll need to tighten your code to:
Only allow the user to write to the document with their own UID both as the document ID and in the uid field.
Only allow the user to write the name field in that document.
If you modify your rules like that, they allow exactly what the code does and nothing else, so there is no room for anyone to abuse them.
So the proper create rule would be:
match /users/{userID} {
allow create: if request.auth.uid == $userID &&
request.document.data.uid == $userID &&
(request.resource.data.keys().hasOnly(['name', 'uid']));
}
You should go through each of your rules like that and through each piece of code that accesses the database, and give the minimum permission that allows the code to work.

Firestore security rules: How to constrain queries that use get(<document_path>)?

So I have a query (that fails). It reads like this: "As I user I can list all the businesses, which I'm a part of, for an organization".
fs
.collection('businesses')
.where('organizationUid', isEqualTo: 'some-organization-id')
.get();
And a security rule to protect it (the gist of it):
match /businesses/{businessId} {
function isStaffOrHigher() {
return get(/databases/$(database)/documents/businesses/$(businessId)/users/$(request.auth.uid)).data.role >= 50;
}
allow read: if isStaffOrHigher();
match /orders/{orderId} {
allow read, write: if isStaffOrHigher();
}
match /users/{userId} {
allow read: if request.auth.uid == userId || isStaffOrHigher();
}
}
Basically it looks up the user's role in his user document (that is owned by that business). This type of rule (that uses the get() operator) works for sub-collections (there's no problem querying orders, for example) of {businessId}, but not for queries that attempts to list the businesses.
Now I KNOW that the organizationUid is a valid constraint, but having read Rules are not filters, I can understand why Firestore can't validate this claim without reading a ton of data.
The question is just, then how do I solve this? And how does Firestore validate the constraint for sub-collections correctly?
Security rules won't do what you want because it would involve reading another document for every document matched by the query. The rules can't know ahead of time what those documents are going to be, as there are variables (businessId) in the path. If this query would yield a millions of documents the businesses collection, you can see how it would be problematic (and expensive for you) to read each of the matching documents from /businesses/$(businessId)/users/$(request.auth.uid) to find out if the entire query should be allowed rejected.
Rules must operate extremely quickly in order to scale in the way that Firestore needs to scale. The limitation that rules can't be filters is part of that requirement for scalability. It's also why there is an overall limit of 10 documents read with get() per rule evaluation.
There is no workaround here from a rules perspective, other than to perform multiple queries, each within bounds of your rules for each collection, and merge the results in your client app.

Cannot Query in Firestore Rules Using Claims

In my firestore rules, this allows me to query a list of all of a user's "organizations":
match /organizations/{orgId}{
allow read: if request.auth.uid in resource.data.members;
}
Where members is an array of user id's.
But, if I change this to work with claims:
match /organizations/{orgId}{
allow read: if orgId in request.auth.token.organizations;
}
Where organizations is a list of organization id's.
It seems to work with:
match /organizations/{orgId}{
allow read: if request.auth.token.organizations[orgId] == true;
}
It will let me access the document, but not a list of documents. Is there any way around this?
This doesn't work because security rules are not filters. (Be sure to read the docs in that link.) Also read more here.
When you perform a query on a collection (not a single document get), the filters on the query must absolutely match the requirements of the rules, before the contents of any documents are known. The security rules will not remove individual documents from the results. In this respect, Firestore queries are "all or nothing" - either all of the requested documents are known to match ahead of time, or the entire query fails.
What you have now suggests that each document ID should be read and individually compared to the list of organizations to determine which ones should be returned. Since rules won't do this filtering, it simply simply rejects the query altogether.
What you should probably do instead is simply make one get() for each org ID in the user's claims. It's definitely possible to read custom claims in the client app.

Understanding Firebase firestore rules

I want setup my firebase rulse . I want register users according to their district and my District stored in my firestore database . So when on user going to register but that user not logged in to the app so i want setup my rules like Any one can read District Collection and other collection must be authentication
so i setup my rules like this is it correct ?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
match /District/{document=**} {
allow read :if true;
}
}
}
The rules should do what you suggest -- allow unauthenticated reads to anything in /District and allow authenticated users the ability to read and write the entire database.
Three things though:
First, you should familiarize yourself with the simulation ability of the rules console. It lets you directly test specific scenarios by hand, to explore what is going on in your rules.
Second, Firebase provides a way to write tests for security rules using the emulator. This is good practice to ensure that you have a comprehensive suite of tests that is repeatable as your application grows in complexity.
Finally, realize that even with this set of rules, you have given any authenticated user the full run of the database -- they can literally read or write any data without restriction (this is definately not a recommended configuration). This would include, for example, deleting the entire /District collection. This may or may not be acceptable for your use case, but you should consider the choice very carefully. Keep in mind that users are able to run arbitrary code against your database (not just the code of your client app), subject only to the permissions granted by security rules.

Firestore rules - securing data by field

I have a field on my "feed" documents that represents the userId. I want to secure my data such that only the user can read/write feed entries with their own userId. Everything else - allow the user to read/write as long as they are authenticated.
I'm a little new to firestore, but so far I have something like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /feed/{feedItem} {
allow read, write: if request.auth.uid == resource.data.userId
}
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
Unfortunately, this is still allowing read/write access to everything, including feed items.
Those rules are not going to work the way you expect.
Your rule to limit reads and writes on document in the feed collection looks OK, assuming that the field name seen here matches the name of field in the documents.
However, your rule for "everything else" with match /{document=**} is not OK. This rule always matches every document in your entire database, no matter what your other rules are. So, as you have it written right now, every user can still read and write every document, including those in "feed" because that rule always allows it.
It's good to keep in mind the following statements about the way security rules work:
If any rule would allow access to some document, then access will be allowed.
Once access to a document has been allowed by a rule, that access cannot be revoked by another rule.
In fact, security rules don't have a way to specify "everything else" in relation to some other rule. What you'll need to be do instead is call out each collection by name in its own rule, and assigning access to it. Yes, this is cumbersome if you have a lot of collections, but it's your only option.