How to validate this data structure with Firebase security rules? - firebase

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.

Related

Is there a way of overwriting Firebase rules at a lower level in the hierarchy? [duplicate]

{
"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?

Firebase security rules to read partial objects [duplicate]

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'])"
}
}
}
}

Firebase structuring data, keeping some data private [duplicate]

{
"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?

Firebase data Structure for a twitter clone

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"
}
}
}
}
}

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