Firebase security rules to read partial objects [duplicate] - firebase

This question already has an answer here:
Firebase: How to structure public/private user data
(1 answer)
Closed 4 years ago.
I can't figure out how to filter data using Firebase database. I've read that rules can't be used for filters. But then how?
I'd like a datastructure somewhat like the one below. i.e. a list of posts created by different users due for a specified time (user-id is not included in the layout below as I'm not sure where to put it)
posts: {
"-LKwbZsfy55d24kwX4t1" : {
when: {
from: "2019-01-01 10:00",
to: "2019-01-01 11:00"
content: {
text: "Hello"
}
},
"-LKwbZsfy55d24kwX4t2" : {
when: {
from: "2019-01-02 10:00",
to: "2019-01-02 11:00"
content: {
text: "Another hello"
}
}
}
I would like everyone to be able to read all posts so my sync path is '/posts'
BUT only the user that created the post should be able to see the 'content'. So I somehow need to say that posts has ".read" : true, and content has ".read": $uid == auth.uid (which is not possible since access cannot be revoked by a child path)

If your current data structure makes it impossible to secure the data to your needs, considered restructuring it so that security rules become possible. In other words, don't nest protected data under public data. Put protected data in its own top-level child.
"posts-public": {
"-LKwbZsfy55d24kwX4t1": {
// public data here
}
},
"posts-private": {
"-LKwbZsfy55d24kwX4t1": {
// private data here
}
}
Now you can write security rules to protect them independently from each other.

".read": "true", gives everyone to read data
And it should be looks like this (just for example):
"posts": {
".read": "true",
"$postId": {
".read": "true",
".validate": "root.child('posts/'+$postId).exists()",
"$contentId": {
".read": "auth !=null",
".write": "auth != null",
".validate": "(newData.hasChildren(['content']))",
"content": {
".validate": "newData.val().length > 0"
},
"user": {
".validate": "newData.hasChildren(['id', 'name', 'avatar'])"
}
}
}
},
"privatePost": {
"$uid1": {
"$uid2": {
".read": "auth != null && ($uid1 === auth.uid || $uid2 === auth.uid)",
"$postId": {
".write": "auth != null",
".validate": "(newData.hasChildren(['content']))",
"content": {
".validate": "newData.val().length > 0"
},
"user": {
".validate": "newData.hasChildren(['id', 'name', 'avatar'])"
}
}
}
}

Related

Firebase realtime database rules to edit only existing fields

My database:
I want to set firebase rules where user can update existing tags value but not create new tags
If you only want the user to be able to update existing properties, and not create any new properties, that'd be:
{
"rules": {
"$uid": {
".write": "auth.uid === $uid"
"$property": {
".validate": "data.exists()"
}
}
}
}
So this rule validates that a property can only be written if there's already data for that property in the database.
If you only want the user to be able to write a name and address, and not any other properties, that'd be:
{
"rules": {
"$uid": {
".write": "auth.uid === $uid"
"name": {
".validate": true
},
"address": {
".validate": true
},
"$other": {
".validate": false
}
}
}
}
In the above rules we allow writing the named name and address properties, and reject any other properties with a wildcard capture rule.

What is the proper way of storing timestamps in Firebase if we don't want them to be editable?

Suppose I have a messages node in my database with this structure:
"messages": {
"$messageId": {
"text": "Hello there!",
"created_by": "$userId",
"created_at": 1501749790029
}
}
and this rule:
"messages": {
".read": "auth != null",
"$messageId": {
".write": "auth != null",
// required fields
".validate": "newData.hasChildren(['text', 'created_by', 'created_at'])"
}
}
Seems pretty standard. But my problem is, this structure and rule allows any user to alter the value of created_at to any value, right? The property created_at should be a timestamp of when the message is pushed and should not be editable.
Am I correct if I re-structure my database like this:
"messages": {
"$messageId": {
"text": "Hello there!",
"created_by": "$userId"
}
},
"created_at": {
"$messageId": 1501749790029
}
Basically, I will move created_at to a separate node so it cannot be edited by the user. I will then set up an event trigger via Cloud Functions that will auto-push the timestamp at created_at when a new message is pushed to messages.
The way to do this is to allow setting the created_at value only if there wasn't a previous value, here's the doc
"messages": {
".read": "auth != null",
"$messageId": {
".write": "auth != null",
// required fields
".validate": "newData.hasChildren(['text', 'created_by', 'created_at'])"
"created_at": {
".write": "!data.exists()"
}
}
}
Also, I think you may want to validate the owner of the message to prevent users from posting as other people adding this:
"created_by": {
".validate": "newData.val() == auth.uid"
}
After further researching, I found out I can make use of .validate and the now variable on Firebase rules to prevent invalid timestamps:
"messages": {
".read": "auth != null",
"$messageId": {
".write": "auth != null",
".validate": "newData.child('created_at').val() == now && newData.hasChildren(['text', 'created_by', 'created_at'])"
}
}
This is less complicated than the one I thought of doing. Amazing.

Firebase Database Rules

I'm having some issues wrapping my head around the database rules and the documentation isn't helping. I am trying to set things up so that only the user can delete their own items, however at the moment I'm getting permission_denied errors. I am assuming that it is because I don't have a read/write rule on the 'items' level. However I feel that if I just added a 'auth != null' rule it would give to much permission. Any thoughts?
the database setup:
users:
{
user_1 {
items:
{
item_1: true,
item_2: true,
}
},
user_2 {...},
etc {...},
},
items:
{
item_1
{
user: "user_1"
....
},
item_2
{
user: "user_1"
....
},
}
The database rules look like
{
"rules": {
"users": {
"$uid":{
".read": "auth != null && auth.uid==$uid",
".write": "auth != null && auth.uid==$uid"
}
},
"items": {
"$itemID": {
".read": "root.child('Users/'+auth.uid+'/'+$itemID).exists()",
".write": "root.child('Users/'+auth.uid+'/'+$itemID).exists()"
}
}
}
}
At the moment any user can delete any item.
To ensure that only the owner can delete the item, you need to not just verify that:
"items": {
"$itemID": {
".read": "auth.uid == data.child('user').val()",
".write": "auth.uid == data.child('user').val()"
}
}
There is no need to check if they exist in the /users node as far as I can tell, although you can easily add that back if needed.
But if a user can only read/write their own items, I'd model the data differently:
"items": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid"
"$itemID": {
}
}
}
This is much simpler to model and will give you much better scalability, since the database only ever has to consider the data for one user.

Validate a Firebase Key is an integer

Here is the database schema:
Here are the rules:
"notifications": {
"$year": {
".read": "false",
".write": "!data.exists()",
"$month": {
".read": "false",
".write": "!data.exists()",
"$day": {
".read": "false",
".write": "!data.exists()",
"$hour": {
".read": "false",
".write": "!data.exists()",
"$minute": {
".read": "false",
".write": "!data.exists()",
"$data": {
".read": "false",
".write": "!data.exists()"
}
}
}
}
}
}
How can I validate (using ".validate" or ".write" rules) that the users can enter only integers into that tree? Or is there some workaround?
What I am trying to achieve is to create write only (no deletes, or updates) log that has some structure and will be processed later. I can change the structure for example to something like 2015-10-6-17-30 for the key, or something else. I just can't believe that Firebase does not have something for this situation.
Update:
This is not duplicate, I am searching for a workaround, or something else that will help me achieve what I am after.
To validate that a key is a number:
{
"$key": {
".validate": "$key.matches(/^[0-9]+$/)"
}
}
But please read about array-like behaviors in Firebase. Hint: probably use a prefix like "y2015", "m12", etc. to avoid some unexpected results with using numbers.
If using push IDs works for you, here's a security rule structure you could use.
{
"notifications": {
"$notification_id": {
".write": "!data.exists()",
".read": "false",
".validate": "newData.hasChildren(['time', 'state', 'message'])",
"time": {
".validate": "newData.val().matches(/YOUR REGEX/)"
},
"state": {
".validate": ""
},
"message": {
".validate": ""
}
}
}
}
Obviously you'll need to fill in the blanks. The main thing here is that you can use a regex to match the time field.
The actual data would look like:
{
"notifications": {
"-K-z5koYf8mYZu5OfSGR": {
"time": "2015-10-06-17-30",
"state": 1,
"message": "foo"
},
"-K-z5koYf8mYZwgwsfGx": {
"time": "2015-10-06-17-30",
"state": 1,
"message": "bar"
}
}
}

Firebase Security Rules: Validate which fields are allowed in an object

I am trying to prevent unwanted fields to be added to my user objects.
--> A user can have a phone and a username (but doesn't have to --> newData.hasChildren(['phone', 'username']) doesn't work here).
I first tried this:
"users": {
"$uid": {
"$other": {
".validate": "['phone', 'username'].indexOf($other) > -1"
}
}
}
I get an error because of the array :(
So then I thought about doing something like this but it's really not great if I have many potential fields
"$other": {
".validate": "$other === 'phone' || $other === 'username'"
}
Finally, I created a node in my Firebase called 'rules' and did this:
"rules": {
"users": {
"fields": {
"phone": true,
"username": true
}
}
}
And then my new validation rule in my user object is:
"$other": {
".validate": "root.child('rules/users/fields/'+$other).val() === true"
}
My question is: Is this a correct way to restrict field names ?
Thank you very much for your answer :) I am pretty new to Firebase but I am having a lot of fun trying it out!
To restrict an object in your Firebase to only have the specified keys, try using one additional wildcard child that will match any attributes not already specified, and reject the write if it contains one of these unmatched attributes:
"rules": {
"users": {
"$userid": {
".validate": "newData.hasChildren(['phone', 'username'])",
"phone": {
".validate": "newData.isNumber()"
},
"username": {
".validate": "newData.isString()"
},
"$other": {
".validate": false
}
}
}
}

Resources