firebase rule - retrieve only items where child has certain value - firebase

Is there a way to add a firebase security rule that prevents certain items in a collection from being read based on a value within each child item?
My example:
JSON:
orders{
orderA: {
name: x,
company:a
isDeleted: true
}
orderB: {
name: y,
company:a
isDeleted: false
}
}
It would be great to restrict users to be only able to read all orders where isDeleted === false
My Rule as I currently have (NOT WORKING):
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null && data.child('isDeleted').val() === false",
"$ord": {
".write": etc
}
},...
The above doesnt work because "data" doesnt represent the right object - I can only use data inside the "$res" area.
If I remove "&& data.child('isDeleted').val() === false" it works but of course brings back both records.
My request is something like this, so the $res doesn't apply - as I'm getting ALL orders by companyId
http://mysite.firebase.io/orders?auth="xyz"&orderBy="companyId"&equalTo="a"
Is it even possible for a "retrieve all" type REST call like this and to filter out certain values via the firebase security rules? Am I just as well to retrieve all and then filter them out once I get them back in the front end??

Firebase's server-side security rules don't filter data. I highly recommend checking out the documentation, and some previous questions on this topic, as it's a very common misconception.
Instead the rules merely ensure that any read (in this case) operation, adhere to your requirements. So for your ".read": "auth !== null && data.child('isDeleted').val() === false", rule that means that the server checks if the user is logged in (they are), and that the node they are reading has a child isDeleted that is false. And since /orders/isDeleted does not exist, the read gets rejected.
You can securely allow access to only undeleted data by combining a query that only selects undeleted nodes with security rules that validate this query. Based on the example in the documentation on query based rules that'd look something like:
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null &&
query.orderByChild == 'isDeleted' &&
query.equalTo == false"
}
}
This will work to get only non-deleted nodes. But since you can only order/filter on one property, you can't then also filter on companyId. You could allow that by introducing a synthesized isDeleted_companyId property, as shown in my answer here: Query based on multiple where clauses in Firebase

Related

How to only allow one field to be written in firebase realtime database security rules?

I want write access to only 1 field that I decide in security rules. I tried with this :
"users":{
".read":"auth !== null",
"$user_id" : {
".write":"newData.children.size() === 1 newData.hasChild('isConnected') && newData.child('isConnected').isBoolean()", // i know this is a little weird, but for prescence, it needs to be able to write to the user enpoint even if the token expired
}
But I get this error: No such method/property 'children'.
Define the rule on the specific field itself, not the parent. Make sure that the parent does not allow writes anywhere, then enable writes for individual fields on their own terms:
"users":{
".read": "auth !== null",
"$user_id": {
"isConnected": {
".write": "newData.isBoolean()"
}
}
}
If you're comparing security rules with Firestore (which does let you get a list of fields in a document), you should know that Firestore doesn't let you express individual rules per field, which is why you have to check for them individually in the rule for the overall document. Realtime Database does let you specify rules for arbitrarily nested children, so you can take advantage of that here.

Firebase database rules, differentiate between create and update

I want to differentiate somehow between creating or updating a list with write rules. Any user can create a new chat, while only a user inside that chat should be able to update it. So basically, I want to have an update rule that checks in another denormalized list if that user is inside that chat before being able to update (similar if not equal to the read rule which works fine) without breaking the auth != null rule for create new chat.
"chats": {
"$chat": {
".write": "auth != null",
".read": "root.child('chats_by_user').child(auth.uid).child($chat).exists()"
}
}
Is there a way to do this?
An update usually means that newData will be different from existing data.
So the rule to only let the user update if he is inside the chat would be:
"newData.val() != data.val() && root.child('chats_by_user').child(auth.uid).child($chat).exists()"
And a create means that there is currently no data under that node.
So the rule to only allow create operations would be:
"!data.exists()"
Now putting it all together:
"chats":{
"$chat":{
".write":"auth!=null && ((newData.val() != data.val() && root.child('chats_by_user').child(auth.uid).child($chat).exists()) || !data.exists() )",
".read":"root.child('chats_by_user').child(auth.uid).child($chat).exists()"
}
}

Firebase rules with wildcards to read data if wildcard is known

I have sign up system where I want only users to sign up if they have a valid secret key which I shall provide to users who want to register. If key is in db, then proceed to sign up. Thus I have generated random non repeated 8 chars and stored them in the real time database in the following structure:
Secrets:
"x5f1n9v0":
"Status" : 1
"C8vT2xxY":
"Status" : 1
And so on
..
{
"rules": {
"secrets":{
"$secret": {
".read": true,
".write": false
}
}
}
}
First question regarding the aboves rules:-
In this case no one can add a new secret key ?
Also the read will only be valid if someone has a valid key from my list ? Nobody can read the whole list ? Any bugs in this ?
Now suppose another set of rules where I want to write to the child of each key iff the user knows the valid id.
If I change the rule for write to true, will this work and no bugs to hack it ?
"rules": {
"secrets":{
"$secret": {
".read": true,
".write": true
}
}
}
}
Thanks
In this case no one can add a new secret key?
With those first rules, only someone with administrative access can add keys.
Also the read will only be valid if someone has a valid key from my list? Nobody can read the whole list?
There is indeed no way to read the entire list with your first set of rules. Someone can only read a secret if they know its key.
With your second set of rules:
"secrets":{
"$secret": {
".read": true,
".write": true
}
}
Now anyone can write any secret. That is probably not what you want. If you want to only allow them to change the data that already exists under an existing key, you'll want to check if there is any data already:
"secrets":{
"$secret": {
".read": true,
".write": "data.exists()"
}
}
If you want them to not be able to change-but-not-delete the existing data, that would be data.exists() && newData.exists(). If you have additional requirements about the data formats users can write, you'll want to add those to a corresponding .validate rule.
Your last set of rules is both invalid and meaningless. It's invalid because $secrets is not defined. But even if it was defined $secrets === $secrets will always be true.

Setting a "publishing time" for Firebase database entries?

I am planning an app that would allow users to create posts which shouldn't be readable by other users until the date/time that the creator of the post has selected.
Is this possible using the Firebase Realtime Database? How would I implement something like this?
I guess that simply implementing it in client code would not be secure, since authenticated users could still GET all posts manually, even the "not yet published" ones? Can I use database rules to do it, even though each post would have their individual publish date/time?
Yes, it's possible with Firebase. All you need to do is to add a flag for each post with the default boolean value of false and a TIMESTAMP. This means that by default, the post cannot be readable by other users. Then you need to use a listener on that TIMESTAMP filed to see when the current date/time is equal with the date/time that the creator of the post has selected. If it's equal then just set the value of the flag to true. This means that the post can be readable by other users. That's it!
You can achieve this also using security rules like this:
//Ensure that data being read is less than or equal with the current date/time.
".read": "data.child('timestamp').val() <= now"
The only solution I can think of is creating an entirely new node scheduledPosts with it's own rules to only allow the creator to see/edit it before the publish date (if that's what you're aiming for).
{
"users": {
"uid_1": {
"someData": "data",
...
}
},
"scheduledPosts": {
"pid_1": {
"postData": "data",
"uid": "uid_1",
"publishDate": 1695840299, // must be in milliseconds since epoch
...
}
}
}
And your scheduledPosts's rules would look as follows:
{
"rules": {
"scheduledPosts": {
"$post_id": {
".read": "root.child('scheduledPosts').child($post_id).child("publishDate").val() < now || root.child('scheduledPosts').child($post_id).child("uid") === auth.uid",
".write": "root.child('scheduledPosts').child($post_id).child("publishDate").val() < now || root.child('scheduledPosts').child($post_id).child("uid") === auth.uid"
}
}
}
}
You can use read rule, with combination of now
The rule will look something like this:
".read": "(auth != null) && (data.child('publish_time').val() < now)"

Preventing child nodes from being removed in Firebase

I am running into a painful issue with Firebase security.
I would like an authenticated user to create children under a child node however not be allowed to delete any of the children.
Please see comments in 'used' node
security rules below:
"users": {
"$userid":{
".read": "$userid === auth.uid",
".write":" $userid === auth.uid && newData.exists()",
//writeable by user
"qrcodevalue":{},
"datesubscribed":{},
//not writeable by user
"confirmed":{".validate":false},
"issubscribed":{".validate":false},
"periodend":{".validate":false},
"stripeid":{".validate":false},
"stripesubscription":{".validate":false},
"subscriptionstatus":{".validate":false},
//user should be able to create children under this node but not delete
"used":{
"$promotionid":{
"dateused":{}
}
},
}
},
Any help would be greatly appreciated.
From the Firebase security documentation on new and existing data:
The predefined data variable is used to refer to the data before a write operation takes place. Conversely, the newData variable contains the new data that will exist if the write operation is successful. newData represents the merged result of the new data being written and existing data.
To illustrate, consider a rule that would allow us to create new records or delete existing ones, as long as data does not already exist at a given path, but not to make changes to the data:
// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"
So for you that would translate to something like this:
//user should be able to create children under this node but not delete
"used":{
"$promotionid":{
"dateused":{
".write": "newData.exists()"
}
}
},
This allows the user to write any data to the node, but not delete it.
If you want them to only create but not change the data, it becomes:
".write": "!data.exists() && newData.exists()"

Resources