Limiting how often a particular user can get data from collection - firebase

I have a collection of usernames that map to their character ids. App allows users to search by username, when username is submitted I get the document from firestore and check if it exists or not.
Right now there are no limits to how fast users can query usernames. Ideally I want to allow to query this collection once every 2s per user.
I was able to find this answer https://stackoverflow.com/a/56487579/911930 but if I understood security rules correctly this example imposes "Global" delay on the collection i.e. if user no.1 queries usernames, user no.2 can't query them for 5s. This is obv not ideal for my use case, as I want this rule imposed per user as opposed to globally.
Is this achievable with security rules?

The link you provide describes a write rate limit, both globally and per user (see the section "The final example is a per-user write rate-limit").
There is no way to implement a read rate limit in Firestore security rules. If that is a hard requirement for your app, the most common approach is to make all read operation go through Cloud Functions, where you can enforce the limit.

Related

Restrict specific object key values with authentication in Firestore

I have an object stored in the Firestore database. Among other keys, it has a userId of the user who created it. I now want to store an email address, which is a sensitive piece of info, in the object. However, I only want this email address to be retrieved by the logged in user whose userId is equal to the userId of the object. Is it possible to restrict this using Firebase rules? Or will I need to store that email address in a /private collection under the Firebase object, apply restrictive firebase rules, and then retrieve it using my server?
TL;DR: Firestore document reads are all or nothing. Meaning, you can't retrieve a partial object from Firestore. So there is no feature at rule level that will give you granularity to restrict access to a specific field. Best approach is to create a subcollection with the sensitive fields and apply rules to it.
Taken from the documentation:
Reads in Cloud Firestore are performed at the document level. You either retrieve the full document, or you retrieve nothing. There is no way to retrieve a partial document. It is impossible using security rules alone to prevent users from reading specific fields within a document.
We solved this in two very similar approaches:
As you suggested, you can move your fields to a /private collection and apply rules there. However, this approach caused some issues for us because the /private collection is completely dettached from the original doc. Solving references implied multiple queries and extra calls to FS.
The second option -which is what the Documentation suggests also, and IMHO a bit better- is to use a subcollection. Which is pretty much the same as a collection but it keeps a hierarchical relationship with the parent coll.
From the same docs:
If there are certain fields within a document that you want to keep hidden from some users, the best way would be to put them in a separate document. For instance, you might consider creating a document in a private subcollection
NOTE:
Those Docs also include a good step-by-step on how to create this kind of structure on FS, how to apply rules to them, and how to consume the collections in various languages

How to limit number of document reads from a humongous collection in firestore when some malicious user queries the data?

I have a firestore collection called letters which holds a public letter from users. In my app, I am using pagination to limit the results to 20 when they go to public letters screen. My concern is that this would work fine from within the app but if some malicious user query the database from let's say postman then I will be billed heavily for all those reads. I have all security rules in place like the user should be authenticated but this needs to be public collection so I can't think of anything else to restrict this. How can I restrict someone to read about 20 documents at time?
There is actually no way to restrict the consumption of a collection based on direct query volume. Renaud's answer proposes to use request.query.limit in security rules, but that does not stop a malicious user from simply making as many calls to the pagination API as they want. It just forces them to provide a limit() on each query. The caller can still consume the entire collection, and consume it as many times as they want.
Watch my video on the topic: https://youtu.be/9sOT5VOflvQ?t=330
If you want to enforce a hard limit on the total number of documents to read, you will need a backend to do that. Clients can request documents from the backend up to the limit it enforces. If the backend wants to allow pagination, it will have to somehow track the usage of the provided endpoint to prevent each caller from exhausting whatever limits or quotas you want to enforce.
As explained in the doc:
The request.query variable contains the limit, offset, and orderBy
properties of a query.
So you can write a rule like:
allow list: if request.query.limit <= 20;
Note that we use list, instead of read. The doc says:
You can break read rules into get and list rules. Rules for get apply
to requests for single documents, and rules for list apply to queries
and requests for collections.

Firestore Rules: Allow or limit request to only once every 24h?

Is there a native or efficient way to restrict the user to load a document from a collection only once every 24h?
//Daily Tasks
//User should have only read rights
//User should only be able to read one document every 24h
match /tasks/{documents} {
allow read: if isSignedIn() && request.query.elapsedHours > 24;
}
I was thinking that I might be able to do this using a timestamp in the user document. But this would consume unnecessary writing resources to make a write to the user document with every request for a task document. So before I do it this way, I wanted to find out if anyone had a better approach.
Any ideas? Thanks a lot!
There is no native solution, because security rules can't write back into the database to make a record of the query.
You could instead force access through a backend (such as Cloud Functions) that also records the time of access of the particular authenticated user, and compare against that every time. Note that it will incur an extra document read every call.
There is no real "efficient" way to do so, neither a native at the moment of writing. And finding an actual solution to this "problem" won't be easy without further extensions.
There are however workarounds like with cloud functions for firebase that open new options for solving various limitations firestore has.
A native solution would be keeping track somewhere in the database when each user last accessed the document. This would, as you mentioned, create unnecessary reads and writes just for tracking.
I would prefer a caching mechanism on the client and allow the user to execute multiple reads. Don't forget that if the user clears the cache on the device, he has to query the document(s) again and won't get any data at all if you restrict him completely that way.
I think the best approach, due to the high amount of reads you get, is to cache on client side and set only a limit control (see Request.query limit value). This would look somehow like below:
match /tasks/{documents} {
// Allow only signed in users to query multiple documents
// with a limit explicitly set to less than or equal to 20 documents per read
allow list: if isSignedIn() && request.query.limit <= 20;
// Allow single document read to signed in users
allow get: if isSignedIn();
}

Firestore dynamically update security rules

Imagine we have Chat application and in this application, we have many rooms, some private and some for everyone. Every room has an admin who can manage users (can invite and remove). Only members of the room can read and write messages. An Admin is a person who created a room in this scenario.
I want to create security rules on room creation and update it on membersChange so only members can read and write the content of the message board.
In this case, that's how it could look like:
databse/rooms/
private1
admin: memberX
members: member1, member2
//only admin can write into members fields
messages
message1...
message2...
message3...
//only members can write and read messages
private2
admin: memberXY
members: member1, member4
//only admin can write into members fields
messages
message1...
message2...
message3...
//only members can write and read messages
So is it possible to create and update security rules from cloud function instead of manually updating them in firebase console? Or is there any way to automate this process?
I noticed that I can deploy security rules using CLI. What should be the process here? When do I call it? How can I get members from the database?
EDIT:
for anyone who wants more information check How to Build a Secure App in Firebase
I would rethink this model. Instead of updating the security rules all the time, I see several viable approaches:
Option 1
You can save which users can access a specific room on Firestore, and then on the security rules you can access the document for the room and see which if the authenticated user is in the list of authorized users. The problem with this is cost, because this will fire an extra database read for every operation, which can get expensive.
Option 2
You can create custom claims for the user using a cloud function, like this:
admin.auth().setCustomUserClaims(uid, {"rooms": "room1,room2"})
Then on the security rules you can check if the user has the claims to a specific room:
match /rooms/{roomId} {
allow read: if roomId in request.auth.token.rooms.split(',');
}
I believe you can also save the claim as an array directly, but I haven't tested it.
For this option you need to take into consideration the size of the token, which has a limit and can cause performance problems if it's too big. Depending on your scenario you can create a smaller set of permissions and then set those to the rooms and the users.
Option 3
You could save the uid of the users who can access each document, and then check if the authenticated user's uid exists on that document. But this can get out of hand if you have too many users.
I would go with option 2 if it makes sense for your scenario. Or you could combine more than one of these techniques. My idea was to show a few of the possibilities so that you can choose what works for you.
Having different rules for each room and dynamicly updating your rules is a bad idea. Here are a couples problems that come to mind with this solution:
Who will be updating the rules?
What happens when two rooms get created at the same time?
What will happen when something goes wrong?
How will you maintain your rules when you have a million rooms?
Also It may be a few minutes before changes to your rules take effect.
Instead you can, first of all, split you datastructure into public rooms and private rooms: database/rooms/public/... and database/rooms/private/....
For securing your private rooms you can take a look at rules conditions and do something like: member can read/write IF his UID is in /members (pseudo code, won't work like this).
You can take a look at this question for an example.

Enforcing user quota in Firebase

What is the best way to enforce per-user quota on data stored in Firebase?
My users will be able to create documents with a unique id on the following path:
/documents/id/contents
The id will be uniquely generated using a transaction. The id will be reserved by using a verification rule (contents.id == auth.id)
However how do I prevent a user from spamming the db (by randomly allocating ids to themselves)? Can I have a rule which counts the number of ids allocated to a user and rejects them if the count is too high?
There's currently no good way to do this.
In some cases you could fully enumerate the children that are allowed to exist (child1, child2, etc), and grant read / write for each one. This won't work for large numbers though or for ids you don't know beforehand.
We do have plans to built features to restrict the number of allowed children and to provide other features to enforce quotas on users.

Resources