Firebase structuring data, keeping some data private [duplicate] - firebase

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

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

What is the best practice to write the rules of Firebase in a situation like this?

Initial approach/problem
The user will be able to write to his own user node. Then the user has to be able to write as many buildings he/she wants and as many depts as he/she wants (there are also rooms but I will leave that aside for now for clarity's sake). The user should be able to read (and write) his own user node, buildings and departments but not the other users' node, buildings and departments.
Basically:
User > user's Building > building's Department (TOTAL read and write permissions)
User > Another User's Stuff (NO Permissions at all)
Here is the database strucure:
{
"buildings" : {
"-L9Bc9aazn3mNiW1elJk" : {
"address" : "",
"comments" : "",
"hasDepts" : {
"-L9FwBmYEnkZQzdFJ4lU" : true
},
"name" : "J house",
"ownerID" : "6hwNde08Wuaa9bfReR28niSbOsF3"
}
},
"depts" : {
"-L9FwBmYEnkZQzdFJ4lU" : {
"comments" : "",
"inBuilding" : "-L9Bc9aazn3mNiW1elJk",
"name" : "Dep 1"
},
},
"users" : {
"6hwNde08Wuaa9bfReR28niSbOsF3" : {
"isAdmin" : {
"-L9Bc9aazn3mNiW1elJk" : true,
}
}
}
Initial approach
Each users node has a child node isAdmin that contains all push keys of the buildings this user has created. Using the same logic, the buildings' node contains a hasDepts node with all the push keys from the depts that the user has created in that building.
If you can help I would really appreciate. Anybody out there?
I am using vue.js to write to firebase like this:
addBuilding: function () {
let userId = firebase.auth().currentUser.uid;
let buildingKey = buildingsRef.push().key;
this.newBuilding.ownerID = userId;
buildingsRef.child(buildingKey).set(this.newBuilding);
usersRef.child(userId).child('isAdmin').child(buildingKey).set(true);
}
Closest solution so far / by #André Kool (partially using the initial approach)
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
},
},
"buildings": {
"$pushKey" : {
".read": "root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid",
".write": "!data.exists() || root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid"
}
},
"depts": {
"$pushKey": {
".read": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid",
".write": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid"
}
},
}
With #André Kool's attempt the simulator allows it to read/write to the buildings/$pushKey node. However while it shows the node on the frontend as soon as it it is created, when we refresh the browser or add a new building the node disappears from the frontend (remains on the database). Firebase it's also not allowing to write to the depts node. Any clues and possible solutions?
UPDATE: another approach.
1) making sure both the buildings and depts nodes both have the ownerId child node, like this:
{
"buildings" : {
"-L9HIbKu5fIe8rfoePgi" : {
"address" : "",
"comments" : "",
"hasDepts" : {
"-L9HIdScisDItysCnMlm" : true
},
"name" : "building 1",
"ownerID" : "6hwNde08Wuaa9bfReR28niSbOsF3"
}
},
"depts" : {
"-L9HIdScisDItysCnMlm" : {
"comments" : "",
"inBuilding" : "-L9HIbKu5fIe8rfoePgi",
"name" : "dep 1",
"ownerID" : "6hwNde08Wuaa9bfReR28niSbOsF3"
}
},
"users" : {
"6hwNde08Wuaa9bfReR28niSbOsF3" : {
"isAdmin" : {
"-L9HIbKu5fIe8rfoePgi" : true
},
"name" : "João Alves Marrucho",
"userEmail" : "joaomarrucho#hotmail.com"
}
}
}
2) Use the ownerId to authorise read and writing on all buildings and depts:
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
},
},
"buildings": {
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
"depts": {
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
}
3) New problem with this approach!
With the rules above, using the simulator, it seems to me like firebase is not allowing the user to read/write to the buildings node but it's allowing to read/write to buildings/$pushKey node. Does firebase needs the user to be able read/write to both parent (buildings) and child node (buildings/$pushKey)?. And if so how can you prevent the user from deleting (.set) the whole buildings node?
I am asking this because if we add ".read": true, ".write": true before the $wildcards it writes the intended database structure while rendering the next rules on the cascade completely useless... So that's no good, but at least it kind of hints where part of the solution may reside.
"users": {
".read": true, <<<<<<
".write": true, <<<<<<
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
},
}, // but is should also be able to write to his own buildings
"buildings": {
".read": true, <<<<<<
".write": true, <<<<<<
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
"depts": {
".read": true, <<<<<<
".write": true, <<<<<<
"$id": {
".read": "data.child('ownerID').val() == auth.uid" ,
".write": "data.child('ownerID').val() == auth.uid"
}
},
}
(Other thoughts)
I don't see why the user shouldn't be able to read/write to the buildings parent node (provided the rules prevent him from deleting the whole buildings node and only grant him full access to the buildings/$pushKeys he creates). Is that complicated? I may be getting this wrong, but doesn't firebase need to scan the buildings node before it knows which one belongs to the user?
If firebase rules can't resolve this issue like this, that means that in theory, every firebase app in which the user needs to read and write its own content to the realtime database, which is often the case, needs to store the information under a users.uid node to make it available to him/her. That seems to go against the "keep your database as flat as possible" firebase general instruction, and it also doesn't play well with the fact that any function with database references that have children in firebase require that data.snapshots nightmarish iteration.
Moreover if you can't design a shallow structure, and the user id will constantly change, how would you go about writing the database references in the Apps firebase configuration: const buildingsRef = db.ref('users/'+ userId!!!!+'/buildings'); : >How to constrain read/write rules to the users that create the nodes while keeping this structure?
There must be an good way to do this without bending over backwards.
?
Here are the rules for each part of your datastructure:
For the users node I removed the more global read and write rules because if they are false they are redundant because that is the default state. And if they are true they will override these rules because rules cascade. To allow users to only read/write their own data:
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
Next is the buildings node. Here you can read if you are the owner (ownerID === your uid) and write if there is no data or you are the owner:
"buildings" : {
"$pushKey" : {
".read": "root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid",
".write": "!data.exists() || root.child('buildings').child($pushKey).child('ownerID').val() === auth.uid"
}
}
And last is the departments node where it gets a little tricky because a user can should only read/write departments in buildings he owns. So we have to check if the user has a building with the pushkey of the department. Here I check if the building with the value of inBuilding in the department has ownerID that is your uid (mind spinning complicated):
"depts" : {
"$pushKey" : {
".read": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid",
".write": "root.child('buildings').child(root.child('depts').child($pushKey).child('inBuilding').val()).child('ownerID').val() === auth.uid"
}
}
Another option is changing your datastructure to something like this where you store buildings and departments under the user id:
{
"buildings" : {
"$uid" : {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid",
"$buildingid" : {
}
}
},
"depts" : {
"$uid" : {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid",
"$deptid" : {
}
}
}
}

Check if email exists in user list using AngularFire2

I want to check if there exists a user that have email = someemail#gmai.com in the Firebase database.
I use this function and is download 50MB of data in my PC after 10 minute I get a response that exists. There is any way that I can get that less than 5 second I don't want to wait 10 minute.
in PHP is like the
SELECT FROM ALL USERS WERE EMAIL = 'george#gmail.com'
This is the database:
"users" : {
user: {
"email" : "george#gmail.com",
"custom_id" : 534253,
"description" : "some small description"
},
1754: {
"email" : "natassa#gmail.com", <---- i want to find if this email from 5000 users exist in database and if exist i want to return true
"custom_id" : 110571,
"description" : "some small description"
},
1755: {
"email" : "george#gmail.com",
"custom_id" : dsgfsdfds,
"description" : "some small description"
},
}
This function give me response after 10 min:
check_if_email_exist(email: string) {
this.users = this.db.list('/user', { query: {
orderByChild: 'email',
equalTo: x,
limitToLast: 10,
}} ) as FirebaseListObservable<Product[]>;
console.log(this.users);
return this.users;
}
OMG I find the problem thanx to https://stackoverflow.com/users/209103/frank-van-puffelen is say something about index and when I look in my chrome console I find warning that says something about index and when I look in the database rules I understand what is missing
I add this inside database rules
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"needs": {
".read": "true",
".write": "auth != null",
".indexOn": "name"
},
"brands": {
".read": "true",
".write": "auth != null",
".indexOn": "name"
},
"products": {
".read": "true",
".write": "auth != null",
".indexOn": "custom_id" <========== This line you want
}
}
}

Firebase - Get items I have read access to from endpoint

TLDR: How can I set up the DB access rules so that I can read all the items I have authorization to from a given endpoint?
I have a similar set of data in my Firebase database:
"employees" : [ {
"first_name" : "Constance",
"last_name" : "Smith",
"createdBy: "vdBoGzI2i9f12er6ZcPjG9AiTip2"
}, {
"first_name" : "Agatha",
"last_name" : "Carlton",
"createdBy: "Tpg1mFR99meDV2QGT44pU6y7s1T2"
},
...
}
I also have a list of application users:
"users" : {
"Tpg1mFR99meDV2QGT44pU6y7s1T2" : {
"name" : "Alex Lund",
"isAdmin": true
},
"vdBoGzI2i9f12er6ZcPjG9AiTip2" : {
"name" : "David Peterson",
"isAdmin": false
},
...
},
Basic users will have access just to the employees they created; the admin will be able to read everything.
{
"rules": {
".write": "auth != null",
"employees": {
"$employee": {
".read": "root.child('users').child(auth.uid).child('isAdmin').val() === true || data.child('createdBy').val() === auth.uid"
}
},
}
With this rules, an admin will be able to read ref.child('/employees/0'), but won't have access to ref.child('employees').
How can I get all the employees I have read access to? Is running a query the only solution?
With the rules as you have them now, a query on /users won't work. Since you don't have read permission on /employees any listener on that location will immediately be rejected.
You probably want the rules to be like this:
{
"rules": {
".write": "auth != null",
"employees": {
".read": "root.child('users').child(auth.uid).child('isAdmin').val() === true"
"$employee": {
".read": "data.child('createdBy').val() === auth.uid"
}
},
}
With these rules administrators can read (and thus query) /users, while regular users can only access children that they created.
This is a common pitfall when it comes to Firebase Database security rules and is typically referred to as "rules are not filters". See the relevant section in the documentation, this original Q&A on it and any of the questions in this list.

Resources