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"
}
}
}
}
}
Related
{
"rules": {
"users": {
"$uid":{
//Private whatever under "uid" but Public is exposed
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
"public": { ".read": "auth != null" }
}
}
}
}
I've created these rules to have users public/private profile
"users/{uid}/public" profile should be accessible by any users those are authenticated, but cannot access the data under "users/uid"
Here is some fake data that is stored in my firebase database.
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
//This public child should be accessible by
//"Example 2" but cannot know the name of
// this user
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
//This public child should be accessible by
//"Example 1" but cannot know the name of
// this user
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
I want to know if this is the robust way to prevent any users from accessing user's critical information! Is there anyway I can improve this by using validate? I am open to any suggestions you guys have. I want to create the best and simple security rules for my app.
You can definitely secure access to the private and public data with your current data structure.
But one use-case you'll likely want at some point is to show a list of the public info for all users. With your current data structure that is not possible, because Firebase's security model cannot be used to filter data. For a great answer covering this, see Restricting child/field access with security rules.
Most developers split the public and private data in completely separate subtrees:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
}
},
"public_profiles": {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"email" : "example1#gmail.com"
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"email" : "example2#gmail.com"
}
}
}
You can then secure access with:
{
"rules": {
"users": {
"$uid":{
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
}
},
"public_profiles": {
".read": "auth != null",
"$uid":{
".write": "auth != null && auth.uid == $uid",
}
}
}
}
Now any authenticated user can listen to /public_profiles, which means you can easily show a list of these profiles.
Hmm wouldn't it be easier to (re)structure the db so that you have a public and a private field per user? Something like:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"private": {
"Name:" : "Example 1"
},
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"private": {
"Name:" : "Example 2"
},
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
/UPD: This way it should be easy(er) to have the different permissions because they won't inherit them from the parent?
I have a structure like this :
posts: {
group:{
postId: {
nbLikes: ..,
nbComments: ...,
updatedAt: ...,
text: ...
}
}
}
and I would like to allow the author to remove it, and everyone else to update everything except the text.
I have tried with this:
"posts": {
"$group": {
"$post": {
".write": "(data.exists() && !newData.exists() && data.child('userId').val() === auth.uid)
|| (data.exists() && newData.exists() && auth.uid != null)
|| (!data.exists() && newData.child('userId').val() === auth.uid)",
"text": {
".write": false
}
},
}
},
but unfortunately it does not work.
Moreover, when I try doing,
"posts": {
"$group": {
"$post": {
"text": {
".write": false
},
"nbLikes": {
".write": true
},
"nbComments": {
".write": true
}
....
},
}
},
it doesn't work either
Thanks for your help !
{
"rules": {
"users": {
"$uid":{
//Private whatever under "uid" but Public is exposed
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
"public": { ".read": "auth != null" }
}
}
}
}
I've created these rules to have users public/private profile
"users/{uid}/public" profile should be accessible by any users those are authenticated, but cannot access the data under "users/uid"
Here is some fake data that is stored in my firebase database.
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
//This public child should be accessible by
//"Example 2" but cannot know the name of
// this user
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
//This public child should be accessible by
//"Example 1" but cannot know the name of
// this user
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
I want to know if this is the robust way to prevent any users from accessing user's critical information! Is there anyway I can improve this by using validate? I am open to any suggestions you guys have. I want to create the best and simple security rules for my app.
You can definitely secure access to the private and public data with your current data structure.
But one use-case you'll likely want at some point is to show a list of the public info for all users. With your current data structure that is not possible, because Firebase's security model cannot be used to filter data. For a great answer covering this, see Restricting child/field access with security rules.
Most developers split the public and private data in completely separate subtrees:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"Name:" : "Example 1",
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"Name:" : "Example 2",
}
},
"public_profiles": {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"email" : "example1#gmail.com"
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"email" : "example2#gmail.com"
}
}
}
You can then secure access with:
{
"rules": {
"users": {
"$uid":{
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
}
},
"public_profiles": {
".read": "auth != null",
"$uid":{
".write": "auth != null && auth.uid == $uid",
}
}
}
}
Now any authenticated user can listen to /public_profiles, which means you can easily show a list of these profiles.
Hmm wouldn't it be easier to (re)structure the db so that you have a public and a private field per user? Something like:
{
"users" : {
"YFIIAgwa2kaannrXjwvSZmoywma2" : {
"private": {
"Name:" : "Example 1"
},
"public" : {
"email" : "example1#gmail.com"
}
},
"YgSfSzPzxLbyDL17r6P9id2cdvH2" : {
"private": {
"Name:" : "Example 2"
},
"public" : {
"email" : "example2#gmail.com"
}
}
}
}
/UPD: This way it should be easy(er) to have the different permissions because they won't inherit them from the parent?
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.
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()",
...
}