Firestore Security Rule for allowing only one document - google-cloud-firestore

I have a collection that should have only one document or none.
Is it possible to create a security rule to validate this condition?
I want to block an add if there's a document there already. In that case only an update is allowed.

It's not possible to do this with security rules without also adding another document that maintains the count of documents, and adding more security rules to require that its count stay up to date. (It doesn't have to be a count - it could be a boolean - but it will have to be atomically up to date regardless.)
See also: How do you force a Firestore client app to maintain a correct document count for a collection?

Related

Firestore Rule - limiting "list" access

I have a collection on which I want to provide list access, but only in a limited manner for most users.
All users should be able to do this: (the string valuex can be anything)
collection("XYZ").where("fieldx", "==", "valuex").get()
Only admins can get all the documents:
collection("XYZ").get()
Note that as valuex can be anything, at the end of the day all users can see all documents. The difference is that non-admins need to know what to query, admins don't, they get it all directly.
The only solution I have found is to force non-admins to write to a document the value they are querying, prior to calling get. The rules then are:
allow list: if isadmin() || resource.data.fieldx == getvaluex();
function isadmin() { return request.auth.token.get("admin", false); }
function getvaluex() { return get(/databases/$(database)/documents/users/$(request.auth.uid).data.valuex; }
That way all returned documents must have the same value for fieldx. But this solution 1) needs 1 additional write 2) adds a read in the rules and 3) in my case valuex is sensitive and I dont want the user to have to store it in Firestore.
So is there any better solution?
Is it possible for instance to limit the usage of an index to only some users? (both queries above actually have more where statements and require each a specific composite index).
Is it possible to compare the returned documents between each others to ensure they all have the same value for fieldx?
The way I would do it is this:
Don't allow non-admins to make those direct requests to the database at all.
Instead, have them send a request to a Firebase Http function.
The Http function has admin access to the db, it can accept any valuex non-null value.
It queries the db using that valuex, on behalf of the non-admin users, and returns the results.
This way, you can keep the documents in collection XYZ locked to non-admins in your Firestore Rules.
You can even keep sensitive data in those documents, since you have control on what you share with users. You can control that by choosing which fields your HTTP function will return to clients.
Mind you, Firebase function invocations are way cheaper than making additional writes/reads.
Firestore works well for easy/normalized access from clients to collections and documents.
What you are trying to do is pretty specific to your implementation of the these "lists".
You may create another collection (list_auth) that tracks the accesses to the list.
In the security access you can create a security rule for the collection that looks up the permissions of user into the list by accessing the list_auth collection.
https://firebase.google.com/docs/firestore/security/rules-conditions#access_other_documents

Firestore! setting limits for documents created per day in 'Rules'

How can I set posts 'Documents' limits that can the user create per day, for example I want to set the limit for 5 posts per day, so is there any rule something like:
match /Post/{id} {
allow create: if ........
&& getAfter(/databases/$(database)/documents/Post/$(id))*LAST 5*".data.createdDate <= 24H....
}
What you're trying to do isn't possible with security rules without some of your own record-keeping. Since security rules don't let you perform queries other than single-document get(), you won't be able to find out what the user has done to a collection by looking at the documents in the collection itself. You will have to maintain some sort of per-user record in a single document with a summary of what they've done over time, then use that known document in a rule that would deny access if they have already exceeded the limits you set. There is nothing very easy or straightforward abut this, and you might be better off requiring the user to go through a backed that enforces the limits instead of using security rules.

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.

Firestore security rules based on individual documents?

In my Google Firestore collection, I have a series of documents. Each of these documents have their own data. Included in each of these documents is a field called api_key;
Using Javascript, how would I make it so that a set/update command can only be accepted if the command properly includes the value in api_key. Using the api_key, the script should be able to set/update any content within that document tree... and only that document tree.
Is something like this possible?
I don't believe this is possible currently (without using a deprecated expression, which is strongly discouraged), because security rules don't allow you to distinguish between a value provided for a field in a document update, and the value of an existing field with the same name. It's tempting to try this to try to compare the provided value with the existing value:
allow update: if "apiKey" in request.resource.data
&& request.resource.data.apiKey == resource.data.apiKey;
But when you say request.resource.data.apiKey, that will evaluate to either the existing field value in the document, or the provided value. So, if someone simply didn't provide apiKey in the update, the security rule would just provide the existing apiKey value, and the write would be allowed. This rule would just reject writes where the apiKey is provided by doesn't match the existing value.

Where to extend collection documents with computed fields in Meteor?

We have information we need on the client which is computed on a document. Like for example the number of entries in an array.
More practically we have a document Workshop which helds an array of participants (user's _id). Now we want the Workshop.numberOfParticipants().
There is no need to transmit the whole array to the client, so where to calculate this value? Is it possible to add this value to the document "Workshop" as a field like the other data?
I like to circumvent the generation of a Template.workshop.numberOfParticipants().
One option for the future is MongoDB's oddly-named aggregation framework. Queries written against the aggregate API can return documents with calculated fields.
Meteor core doesn't support aggregate queries yet, but it's on the wishlist.
You'll need to publish a set of documents called NumParticipants and then add an observer that updates a count property or something similar when documents are added (and similarly reduces that property when docs are removed).
An example of how to do this is described in the documentation for publish.