I launched my first open repository project, EphChat, and people promptly started flooding it with requests.
Does Firebase have a way to rate limit requests in the security rules? I assume there's a way to do it using the time of the request and the time of previously written data, but can't find anything in the documentation about how I would do this.
The current security rules are as follows.
{
"rules": {
"rooms": {
"$RoomId": {
"connections": {
".read": true,
".write": "auth.username == newData.child('FBUserId').val()"
},
"messages": {
"$any": {
".write": "!newData.exists() || root.child('rooms').child(newData.child('RoomId').val()).child('connections').hasChild(newData.child('FBUserId').val())",
".validate": "newData.hasChildren(['RoomId','FBUserId','userName','userId','message']) && newData.child('message').val().length >= 1",
".read": "root.child('rooms').child(data.child('RoomId').val()).child('connections').hasChild(data.child('FBUserId').val())"
}
},
"poll": {
".write": "auth.username == newData.child('FBUserId').val()",
".read": true
}
}
}
}
}
I would want to rate-limit writes (and reads?) to the db for the entire Rooms object, so only 1 request can be made per second (for example).
Thanks!
The trick is to keep an audit of the last time a user posted a message. Then you can enforce the time each message is posted based on the audit value:
See it in action in this fiddle. Here's the gist of what's in the fiddle:
The existing answers use two database updates: (1) mark a timestamp, and (2) attach the marked timestamp to the actual write. Kato's answer requires 500ms time-window, while ChiNhan's requires remembering the next key.
There is a simpler way to do it in a single database update. The idea is to write multiple values to the database at once using the update() method. The security rules validates the written values so that the write does not exceed the quota. The quota is defined as a pair of values: quotaTimestamp and postCount. The postCount is the number of posts written within 1 minute of the quotaTimestamp. The security rules simply rejects the next write if the postCount exceeds a certain value. The postCount is reset when the quotaTimestamp is staler than 1 minute.
Here is how to post a new message:
The security rules to rate limit to at most 5 posts per minute:
Note: the serverTimeOffset should be maintained to avoid clock skew.
I don't have enough reputations to write in the comment, but I agree to Victor's comment. If you insert the
fb.child('messages').push(...)
into a loop (i.e.for (let i = 0; i < 100; i++) {...}
) then it would successfully push 60-80 meessages ( in that 500ms window frame.Inspired by Kato's solution, I propose a modification to the rules as follow:
The .js code is quite similar to Kato's solution, except that the getTimestamp would return {time: number, key: string} to the next callback. Then we would just have to
ref.update({[key]: data})
This solution avoids the 500ms time-window, we don't have to worry that the client must be fast enough to push the message within 500ms. If multiple write requests are sent (spamming), they can only write into 1 single key in the
messages
. Optionally, the create-only rule inmessages
prevents that from happening.