How to add index rules for Firebase database? - firebase

I keep getting firebase messages on console regarding to add indexOn for user 4321 at chat rules. Below is my database.
And my rules in firebase is like:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"users": {
".indexOn": ["name", "email","uid"],
"$uid": {
".write": "$uid === auth.uid"
}
},
"chats": {
"$key": {
".indexOn": ["uid"]
}
}
}
}
I'm not sure why the indexing not working. And how can I improve indexing for overall database?

Your current data structure only allows to easily list all members of a chatroom, not the other way around. That may be the reason you get that message, because if you want to list all chats that user belongs to, you have to search through all /chats records.
You probably need to duplicate the chat room membership data both at /chat/<chat-id>/members and /users/<uid>/groups. Your case is almost identical to the one in the Firebase guide here -- read particularly the description below code in the section linked, but it's best to read the whole guide, it really helped me to understand how the database works.
Btw: your rule in chats: ".indexOn": ["uid"] doesn't do anything with the sample data you posted. It says to "index chat rooms by their uid attribute", but your chat rooms don't have an uid key inside (meaning uid: 'someid', not 'someid': true). See the indexing guide on more info how indexing works.

There are two types of indexing orderByValue: and orderByChild
Indexing with orderByValue::
{
"rules": {
"scores": {
".indexOn": ".value"
}
}
}
JSON Tree
{
"employee": {
<"employee1Key">: {
"empName": Rohit Sharma,
"Designation": SW Developer,
"empId": EMP776,
"branchDetails": {
"branch": Software,
branchLocation: Pune,
branchId: BR556
}
},
<"employee2Key>": {
"empName": Vira tKholi,
"Designation": Accountant,
"empId": EMP908,
"branchDetails": {
"branch": Finance,
branchLocation: Dheli,
branchId: BR557
}
},
<"employee3Key">: {
"empName": MS Dhoni,
"Designation": Sales Manager,
"empId": EMP909,
"branchDetails": {
"branch": Sales and Marketing,
branchLocation: Pune,
branchId: BR556
}
}
}
}
Indexing with orderByChild:
{
"rules": {
"employee": {
".indexOn": ["empName", "empId", "designation"]
}
}
}
Multilevel Indexing:
{
"rules": {
"employee": {
".indexOn": ["empName","empId","branchDetails/branch",
"branchDetails/branchLocation","branchDetails/branchId"]
}
}
}
You can also refer to this link for the Firebase Real-Time Database Data Indexing.
Firebase Real-Time Database Data Indexing

Related

Firebase read permissions

So I've set up a simple db/web form to collect some user data. Rn I am trying to figure out the rules thing but I am running into this problem - if my read flag is set to true then I can simply run this in the console
var ref = firebase.database().ref();
ref.on("value", function(snapshot) {
console.log(snapshot.val());
}, function (error) {
console.log("Error: " + error.code);
});
and expose the users which should not be the possibility. If I set read to false then I cant access the DB upfront to validate if email address is unique or not. I guess I need to achieve 2 things:
Prevent db snooping through the dev tools running any snippets
Make sure email address is unique.
p.s. My currents rules (prevent delete, prevent read, make sure POST request has certain fields):
{
"rules": {
"users": {
".read": false,
"$uid": {
".write": "!data.exists()",
".validate": "newData.hasChildren(['first_name', 'last_name', 'email', 'country', 'amount'])"
}
}
}
}
To avoid duplicates you will want a validation check in the DB rather than reading data from the client and checking (you can't trust the client).
Since there is no easy way to check for duplicate child values in a firebase base collection, you will need a separate collection to track emails and then validate your emails against that, i.e.:
{
"rules": {
"users": {
".read": false,
"$uid": {
".write": "!data.exists()",
".validate": "newData.hasChildren(['first_name', 'last_name', 'email', 'country', 'amount'])",
"email": {
".validate": "!root.child('emails').child(newData.val()).exists()"
}
}
},
"emails": {
".read": false,
".write": "!data.exists()"
}
}
}
You will then need to write the users' emails to the email collection as the users are added, e.g.:
var ref = firebase.database().ref('emails/'+email);
ref.set(uid)

How to constraint a child property when reading a parent list

I have the following database schema:
{
"events": {
"$eventId": {
"eventTitle": "Go shopping",
"participants": {
"0": {
"id": "0",
"name": "John Smith"
},
"1": {
"id": "1",
"name": "Jason Black"
}
}
}
}
}
It's an array of events, where each event has a list of participants. How to make a database rule, where:
everyone can get event or list of events,
when getting an event, a full list of participants can only by visible by admin,
when getting an event, if a user is a participant of the event, the list of participants would retrieve only him, noone else,
when getting an event, if a user is not a participant, the participant list would be empty
Here is my try in rule scheme:
{
"rules": {
"events": {
".read": true,
"$eventKey": {
"eventTitle": {
".validate": "newData.isString() && newData.val().length < 100"
},
"participants": {
".read": "root.child('users/'+auth.uid+'/role').val() === 'ADMIN'",
".validate": "newData.hasChildren()",
"$participantKey": {
".read": "($participantKey === auth.uid || root.child('users/'+auth.uid+'/role').val() === 'ADMIN')",
"id": {
".validate": "newData.val() === $participantKey"
},
"name": {
".validate": "newData.isString() && newData.val().length < 100"
}
}
}
}
}
}
}
It does not work, because when I read events list it doesn't respect .read constraint in participants and $participantKey fields. It just retrieves full list of participants all the time.
#edit
In other words. I have this simplified rules:
{
"events": {
".read": true,
"$eventKey": {
"participants": {
".read": false
}
}
}
}
When I query for: events/{eventKey}/participants I still get an object with participants even though the participants read flag is set to false.
But, when I remove .read flag from events, then retrieving data respects .read flag in participants.
#edit2
From documentation:
A .read rule which grants permission to read a location will also allow reading of any descendants of that location, even if the descendants have their own .read rules which fail.
My question is now, how to omit this rule?
Firebase permissions cascade downwards. Once you've given a user a permission on a certain level in the JSON tree, you cannot revoke that permission on a lower level in the tree.
That means that these rules will not work:
{
"events": {
".read": true,
"$eventKey": {
"participants": {
".read": false
}
}
}
}
The ".read": false is ignored by Firebase.
Instead you will have to structure your data in a way that allows your security requirements. This is done by completely separating the types of data that have different security requirements.
{
"events": {
".read": true,
"$eventKey": {
"participants": {
".read": false
}
}
}
"eventparticipants": {
".read": false
"$eventKey": {
/* This is where you store the participants */
}
}
}
So you have two top-level lists: events and eventparticipants. The lists use the same keys for the objects under them: the event id. But since these are two top-level lists, one can be publicly readable while the other is more restricted.
Firebase documentation recommends against using arrays when adding data to the database. The main problem in your code is that you use an array, which is an anti-pattern when it comes to Firebase.
One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write and this is your case.
Because Firebase is a NoSQL database and becase it is structured as pairs of key and valeu, the solution is to use a Map and not an array. Change the way in which you add data in your database and your problem will be solved.

How to structure data with Firebase to allow data to be user specific and also public?

We are building a platform using Firebase Realtime Database and I'm having a bit of a struggle to find the best way to structure our data for private and public access.
Today we have
database: {
items: {
$userUid: {
$itemUid: {
poster_link: "..."
format: "..."
title: "..."
}
}
}
}
All our items are stored under each user in order to make it fast and secure to load.
Our rules are set up like this
{
"rules": {
"items": {
"$userId": {
"$itemId": {
".read": "auth !== null,
".write": "auth !== null"
}
}
}
}
}
So only an authorised user can read and write the data. I could create something like this to allow items to be public if the value is true:
".read": "auth !== null || data.child('public').val() == true"
But this will still be under $userUid
So I was wondering if you have any suggestion on how to structure this example to allow items to be under a user and also seen publicly, not necessary under this user, a bit like Dropbox does when you share something.
You chosen data structure does not take advantage of the flat data principles of Firebase. This will make it very difficult for you to query items of multiple users. For example, how do you get all the public items without drilling into each user?
Similarly, a boolean called public is also not good because you can't extend it to other ACL scenarios. Much better is an ACL object that can be extended in the future.
For example:
items: {
itemsUid: {
[...],
userId: ...,
ACL: { public: true }
}
}
Now you can write the rule:
auth !== null && (root.child(items/ACL/public).exsists() || data.userId === auth.UID)
If in three months you add a concept of friends that can see you posts or followers that can see you items you can simply add friends: true, followers: true to the ACL object and adjust the rule.
You can structure like this
database: {
items: {
$itemUid: {
poster_link: "..."
format: "..."
title: "..."
user: "userid"
}
}
}
now set the rules as
{
"rules": {
"items": {
"$itemId": {
".read": "auth !== null || data.child('public').val() == true,
".write": "auth !== null"
}
}
}
}

Firebase - Sharing Data

I am struggling to design a simple data sharing app using firebase. To illustrate, consider a messaging app similar to facebook:
Users can add friends - the request must be accepted
Users can add posts
Users can view all posts submitted by themselves and friends
I can set up the security rules for this, but filtering or denormalizing data seems overly cumbersome and inefficient. A sample data model might look like:
{
"users": {
"user1": {
"friends": {
"user2": { "accepted": true }
}
},
"user2": {
"friends": {
"user1": { "accepted": true }
}
},
"user3": {
"friends": {}
}
},
"posts": {
"generatedId1": {
"message": "blah blah",
"user_id": "user1"
},
"generatedId2": {
"message": "lorem ipsum",
"user_id": "user2"
},
"generatedId3": {
"message": "hello world",
"user_id": "user3"
}
}
}
Security rules to achieve the above requirements would look something like:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
"posts": {
"$post": {
".read": "root.child('users').child(data.child('user_id').val()).child('friends').child(auth.uid).child('accepted').val() === true",
".write": "newData.child('user_id').val() === auth.uid"
}
}
}
}
It's probably not quite right, but close enough to illustrate. We can see users can only read and write their own user record, can only add posts with their own user id and can read posts where there is a corresponding accepted friend record.
So my question essentially boils down to how to execute a query that will only return the posts a user is allowed to see? Since security rules are not filters, they do not help us in this case.
My understanding so far is that the posts should be denormalized, basically duplicate the post for each user. This is horribly inefficient - if the user has 100 friends, this means duplicating the data 100 times. Retrospective updates (i.e. adding a new friend or removing an existing one) are also complex to manage.
Another option is to retrieve a users own list of friends and retrieve the posts for those users in separate queries, but again, if a user has 100 friends, this would mean executing 100 separate "queries".
Essentially, I'm attempting to implement a many to many relationship. What is the guidance / best practice here?

Getting .read and .write Security Rules To Work For Groups

I am​ having difficulty writing the security rules for​ building a team based collaboration platform.
​​When a user registers they should be able to create a team and invite users to that team.
Projects should be owned by the team​.​
​Only users in that team should be able to view ​that project.​
​Users should only see the teams they are a member of​.
How do I write​ .read​ security rules so the ​users only see info from teams they're in?
I should only get two teams listed because I belong to them github:8272012​.​
​Current Security Rules: ​
{
"rules": {
".read": true,
"users": {
"$user": {
//can add a message if authenticated
".write": "auth.uid === $user"
}
},
"teams": {
"$team": {
"users": {
// can write to the users list only if ADMINISTRATOR
"$user": {
".write":"newData.parent().child(auth.uid).val() === 99"
}
}
}
},
"projects": {
"$team": {
"$project": {
//can add a message if they are a MEMBER
".write": "(!data.exists() && newData.exists() && root.child('teams/' + $team + '/users/' + auth.uid).val() >= 10)"
}
}
}
}
}
I should only get two teams listed because I belong to them github:8272012.
The following security rules would give read and write access for a project only to users who are in that project's team (assuming you add a /projects node for each user to indicate which projects that user has access to):
"rules": {
"projects": {
"$project": {
".read": "root.child('users').child(auth.uid).child('projects').val().child($project).exists()" ,
".write": "root.child('users').child(auth.uid).child('projects').val().child($project).exists()"
}
}
}
I can't see what data you're storing for each project, but if you store a reference to the project's team you could also use that in your security rules.

Resources