Please consider the follow rule:
"list" : {
"$item" : {
".validate": "newData.hasChildren(['field1', 'field2'])",
"field1" : {
".validate": newData.isString()
},
"field2" : {
".validate": newData.isString()
},
"$other" : {
".validate": false
}
}
}
}
It should only allow you to build a list of objects of a certain type. However, there's nothing to prevent the following:
{
"list" : "i've been hacked"
}
Only thing I've been able to come up with is to add this to the "list" rules:
".validate": "!newData.isString() && !newData.isNumber() && !newData.isBoolean()"
.. which is a bit clunky. Is there a better to enforce this or is my whole approach just wrong-headed?
You could just add .write: false to the list itself, and only enable it on the $item. This way, you can't write to list without validating $item rule.
To elaborate on #cwehrung's answer:
"list": {
".write": false,
"$item": {
".validate": "newData.hasChildren(['field1', 'field2'])",
"field1": {
".write": "newData.isString()"
},
"field2": {
".write": "newData.isString()"
}
}
}
Another option:
"list": {
".validate": "newData.hasChildren()",
...
}
Related
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'])"
}
}
}
}
I'm trying to create a twitter clone to learn to use Firebase and I would love to get a suggestion about how to create the database structure. My biggest concern is related to followers and how to create a timeline when you are following for example 500 users. You would need to perform 500 queries and sort somehow for datetime.
{
"followers" : {
"cesar" : {
"followers" : {
"cesar2" : true
},
"following" : {
"cesar2" : true
}
},
"cesar2" : {
"followers" : {
"cesar" : true
},
"following" : {
"cesar" : true
}
}
},
"tweet" : {
"cesar" : [ null, {
"content" : "tweet 1"
} ]
},
"users" : {
"cesar" : {
"name" : "César",
"notifications" : true,
"username" : "cesar"
},
"cesar2" : {
"name" : "César2",
"notifications" : false,
"username" : "cesar2"
}
}
}
See Firefeed, Firebase's open-source Twitter clone. It includes a walkthrough of the data structure it uses, which boils down to using a fan-out approach when new messages are posted. Here's a copy of the rules used, which describes the underlying data structure:
{
"rules": {
// All data is readable by anyone.
".read": true,
"people": {
// A list of users with their names on the site.
"$userid": {
// Only the user can write their own entry into this list.
".write": "$userid ==auth.uid"
}
},
"users": {
"$userid": {
// The user is allowed to write everything in their bucket.
".write": "$userid ==auth.uid",
"following": {
// The following list should only contain actual ids from the "people" list.
"$followingid": {
".validate": "root.child('people').hasChild($followingid)"
}
},
"followers": {
// Anyone can add themself to to this user's followers list.
"$followerid": {
".write": "$followerid ==auth.uid"
}
},
"feed": {
"$sparkid": {
// User A can write in user B's feed, but only if A is following B, and only for sparks for which they are the author.
".write": "root.child('users/' + $userid + '/following').hasChild(auth.uid) && root.child('sparks/' + $sparkid + '/author').val() ==auth.uid"
}
}
}
},
"sparks": {
// A global list of sparks (the "firehose").
"$sparkid": {
// Modifying an existing spark is not allowed.
".write": "!data.exists()",
// Every spark should have an author and a body.
".validate": "newData.hasChildren(['author', 'content'])",
// A user can attribute a spark only to themselves.
"author": {
".validate": "newData.val() ==auth.uid"
},
"content": {
".validate": "newData.isString()"
}
}
},
"recent-users": {
// Users can add themselves to the list of users with recent activity.
"$userid": {
".write": "$userid ==auth.uid"
}
},
"recent-sparks": {
// Authors of sparks can add their sparks to this list.
"$sparkid": {
".write": "root.child('sparks/' + $sparkid + '/author').val() ==auth.uid"
}
},
"search": {
"firstName": {
"$searchKey": {
".write": "auth != null && (root.child('people/' +auth.uid + '/firstName').val() + '|' + root.child('people/' +auth.uid + '/lastName').val() + '|' +auth.uid) == $searchKey && newData.val() ==auth.uid"
}
},
"lastName": {
"$searchKey": {
".write": "auth != null && (root.child('people/' +auth.uid + '/lastName').val() + '|' + root.child('people/' +auth.uid + '/firstName').val() + '|' +auth.uid) == $searchKey && newData.val() ==auth.uid"
}
}
}
}
}
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
}
}
}
}
So far not having any luck with Firebase Security rules.
I have this
{
"rules": {
"users": {
"$user_id": {
".read": true,
".write": "auth !== null && auth.uid === $user_id",
"profile": {
".validate": "newData.hasChildren(['first_name', 'last_name'])"
}
}
}
}
}
I send data and for the profile and one of them is blank... it lets it write any way. I wind up with data like so...
{
"users" : {
"simplelogin:25" : {
"profile" : {
"first_name" : "John",
"last_name" : ""
}
},
"simplelogin:26" : {
"profile" : {
"first_name" : "Bob",
"last_name" : ""
}
}
}
}
Any help on how to make the above rules work? Cant seem to get it to validate correctly.
Your validation rule is:
".validate": "newData.hasChildren(['first_name', 'last_name'])"
So the new data is valid if it has first_name and last_name properties.
You're sending this object over:
"profile" : {
"first_name" : "John",
"last_name" : ""
}
This object has a first_name and a last_name property, so it is valid according to your rule.
What you seem to want is that the properties don't only exist, but also are strings and have a minimum length. If that is indeed your requirement, you can write it into your validation rules:
"profile": {
".validate": "newData.hasChildren(['first_name', 'last_name'])",
"first_name": {
".validate": "newData.isString() && newData.val().length >= 10"
},
"last_name": {
".validate": "newData.isString() && newData.val().length >= 10"
}
}
The first .validate ensures that a profile has (at least) first_name and last_name properties. The other .validate rules ensure that they are of the correct type and minimum length.
I have this set of rules:
{
"rules": {
"facebook_users": {
"$user": {
".read": "$user == auth.id",
".write": "$user == auth.id",
"userData": {
"maxProjects": {
".validate": false
},
"userProjects": {
".validate": ???
}
}
}
}
}
}
I'd like to let an user not being able to add a children to the userProjects array if userProjects.length > userData.maxProjects. The user is denied writing in the maxProjects already, due to the ".validate": false rule. How can I compare userProjects' length with maxProjects?
If it's not possible, what is a correct way to do this?
You can use the root special variable along with child() to do this comparison:
{
"rules": {
"facebook_users": {
"$user": {
".read": "$user == auth.id",
".write": "$user == auth.id",
"userData": {
"maxProjects": {
".validate": false
},
"userProjects": {
".validate": "data.val().length < root.child('facebook_users').child($user).child('userData/maxProjects').val()"
}
}
}
}
}
}
Have you read this?
parent()
Gets a RuleDataSnapshot for the parent location.
Return Value: RuleDataSnapshot - The RuleDataSnapshot for the parent location.