How does .indexOn works with users and unique keys? - firebase

I am confused with index rules in firebase. I have this database and I wanted to extract distance_traveled in battery_and_cables. The thing is, 1 is something like a userID set by the user itself so that will vary.
I used this code in pyrebase to extract the distance_traveled
db.child("mileage_maintenance").order_by_child("distance_traveled").get()
but I get
Index not defined, add ".indexOn": "distance_traveled", for path "/mileage_maintenance", to the rules
This is my database:
Any my rules:
{
"rules": {
".read": true,
".write": true,
"mileage_maintenance": {
"battery_and_cables":{
".indexOn": ["distance_traveled"]
}
},
}
}
Is there any way you can go through the 1 and the push key to acquire the distance_traveled?

Firebase Realtime Database queries work on a flat list of the nodes under the location where you run the query. So in your case, the query considers the direct children of the mileage_maintenance node (which is just the node 1 in the screenshot).
You can have wildcards in your rules, like in your case:
{
"rules": {
".read": true,
".write": true,
"mileage_maintenance": {
"$id": {
"battery_and_cables":{
".indexOn": ["distance_traveled"]
}
}
}
}
}
As you can see, I added a $id level here for the 1 node. That now means that you have an index defined on each /mileage_maintenance/$id/battery_and_cables node for the distance_traveled property of each child node under that.
So with this index, you can query a specific /mileage_maintenance/$id/battery_and_cables path for the distance_traveled values. But you can't run a query on just /mileage_maintenance for all distance_traveled values under it.
If you need such a query, you will need to modify/augment your data model to allow it. For example, by introducing a flat list of distance_traveled nodes that you can then query:
"distance_traveled": {
"1000000": "1/battery_and_cables/-M01CT...1hMj"
}
Or
"distance_traveled": {
"1~battery_and_cables~-M01CT...1hMj": 1000000
}
Where the ~ in that last model is just a separate for the segments in that key, since / isn't allowed.
Also see:
Firebase Query Double Nested
Firebase query if child of child contains a value

Related

Accessing object property in Firebase Realtime Database ruleset

I store data about trips in a Firebase Realtime Database. I have defined some user groups. I want only users from group "vips" to read trips that have freePlaces == 0 and all the others to read the remaining trips.
I tried to set such access rules:
{
"rules": {
"trips":{
"$trip":{
".read": "root.child('users').child('vips').child(auth.uid).exists() || $trip.child('freePlaces') > 0"
}
}
}
}
but Firebase tells me $trip has no method/property 'child'.
I tried to do it in a different way
{
"rules": {
"trips":{
"$trip":{
".read": "root.child('users').child('vips').child(auth.uid).exists() || root.child('trips').child($trip).child('freePlaces') > 0"
}
}
}
}
but Firebase says Invalid > expression: left operand must be a number or string.
What went wrong?
I recommend playing close attention to the type of each expression in your rules:
$trip is a string, which explains why you can't call .child on it. If you want to get a child of the node, you can just do: data.child('freePlaces').
root.child('trips').child($trip).child('freePlaces') is a location in the database, which can't be compared with >. If you want the value of the node, use .val().
So combining these, your first snippet should be:
data.child('freePlaces').val() > 0

firebase rule - retrieve only items where child has certain value

Is there a way to add a firebase security rule that prevents certain items in a collection from being read based on a value within each child item?
My example:
JSON:
orders{
orderA: {
name: x,
company:a
isDeleted: true
}
orderB: {
name: y,
company:a
isDeleted: false
}
}
It would be great to restrict users to be only able to read all orders where isDeleted === false
My Rule as I currently have (NOT WORKING):
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null && data.child('isDeleted').val() === false",
"$ord": {
".write": etc
}
},...
The above doesnt work because "data" doesnt represent the right object - I can only use data inside the "$res" area.
If I remove "&& data.child('isDeleted').val() === false" it works but of course brings back both records.
My request is something like this, so the $res doesn't apply - as I'm getting ALL orders by companyId
http://mysite.firebase.io/orders?auth="xyz"&orderBy="companyId"&equalTo="a"
Is it even possible for a "retrieve all" type REST call like this and to filter out certain values via the firebase security rules? Am I just as well to retrieve all and then filter them out once I get them back in the front end??
Firebase's server-side security rules don't filter data. I highly recommend checking out the documentation, and some previous questions on this topic, as it's a very common misconception.
Instead the rules merely ensure that any read (in this case) operation, adhere to your requirements. So for your ".read": "auth !== null && data.child('isDeleted').val() === false", rule that means that the server checks if the user is logged in (they are), and that the node they are reading has a child isDeleted that is false. And since /orders/isDeleted does not exist, the read gets rejected.
You can securely allow access to only undeleted data by combining a query that only selects undeleted nodes with security rules that validate this query. Based on the example in the documentation on query based rules that'd look something like:
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null &&
query.orderByChild == 'isDeleted' &&
query.equalTo == false"
}
}
This will work to get only non-deleted nodes. But since you can only order/filter on one property, you can't then also filter on companyId. You could allow that by introducing a synthesized isDeleted_companyId property, as shown in my answer here: Query based on multiple where clauses in Firebase

Firebase Realtime database rule to avoid duplicate value

I have the following structure in Firebase Realtime database :
{
"e_pass": [
{
"address": "M12, AV Colony",
"epass_id": "RPR/0003",
"epass_vehicle_id": "na",
"from_city": "Raipur",
"from_date": "31-03-2020",
"from_district": "Raipur",
"from_location": "AV Colony Mowa",
"isApproved": "1",
"mobile_number": "99999999",
"time_stamp": 1585577315093,
"user_name": "John Doe",
"vehicle_number": "CG04HX1234"
}
]
}
I am trying to apply the following rule to prevent the duplication of key mobile_number:
{
"rules": {
"e_pass": {
".read": true,
".write": {
"$mobile_number": {
".write" : "!data.parent().hasChild($mobile_number)"
}
}
}
}
}
but it keeps failing. What should be the rule?
With the way you have your data structure now, it's not possible. In general, it's not possible to use security rules to ensure uniqueness of child values among sibling nodes.
Your alternative is to write the value that should be unique as a node name (not a child value) somewhere else in the database that can be queried by security rules to check for existence.
For example, if you had a node called "mobile_numbers", you could populate children under it every time a new e_pass child is added:
"mobile_numbers": {
"99999999": true
}
Then you check for its existence in a rule like this:
root.child('mobile_numbers').hasChild($mobile_number)

Firebase Rules: Read restriction for dynamic child nodes

I'm trying to implement a Firebase rules read restriction in a data model that has a few nested dynamic child nodes.
I have the following data model:
/groupMessages/<groupId>/<messageId>/
{
"senderId": "<senderId>",
"recipientId": "<recipientId>",
"body": "..."
}
groupId, messageId, senderId and recipientId are dynamic ids. I would like to attach a listener to the /groudId node to listen to new messages. At the same time I only want users to read the message where the senderId or recipientId matches a corresponding auth.token value.
Due to Firebase cascading rules, if I allow the read at the groupId level without restrictions, I can't deny them on the message level.
{
"rules": {
"groupMessages"
"$groupId": {
".read": "auth != null"
}
}
}
}
I also haven't found a way to restrict the read rule on the groupId level to check for sender/recipientId of a message.
Any suggestions greatly appreciated.
As you've found, security rules cannot be used to filter data. But they can be used to restrict what queries can be performed on the data.
For example, you can query for all messages where the current user is the sender with:
var query = ref.child("groupMessages").child(groupId).orderByChild("senderId").equalTo(uid);
And you can secure access to the group's messages to only allow this query with:
{
"rules": {
"groupMessages": {
"$groupId": {
".read": "auth.uid != null &&
query.orderByChild == 'senderId' &&
query.equalTo == auth.uid"
}
}
}
}
The query and rules now exactly match, so the security rules will allow the query, while they'd reject a broader read operation. For more on this, see query based rules in the Firebase documentation
You'll note that this only works for a single field. Firebase Database queries can only filter on a single field. While there are workarounds by combining multiple values into a single property, I don't think those apply to your scenario, since they only work for AND queries, where you seem to want an OR.
You also seem to want to query on /groupMessages instead of on messages for a specific group. That also isn't possible: Firebase Database orders/filters on a property that is at a fixed path under each child of the node where you run the query. You cannot query across two dynamic levels, as you seem to be trying. For more on this see: Firebase Query Double Nested and Firebase query if child of child contains a value.
The common solution for your problem is to create a list of IDs for each user, which contains just the IDs of all messages (and/or the groups) they have access to.
userGroups: {
uid1: {
groupId1: true,
groupId2: true
},
uid2: {
groupId2: true,
groupId3: true
}
}
With this additional data structure (which you can much more easily secure), each user can simply read the groups they have access to, and your code then reads/queries the messages in each group. If necessary you can add a similar structure for the messages themselves too.
Finally: this type of recursive loading is not nearly as inefficient as many developers initially think, since Firebase pipelines the requests over an existing connection.

How can I grant access to array of admins on Firebase database rules

I am stuck trying to allow an an array of admins access to their data.
I have a database structure like this:
{
"Respondents": {
"Acme Corp": {
"admins": ["mMK7eTrRL4UgVDh284HntNRETmx1", ""mx1TERNmMK7eTrRL4UgVDh284Hnt"],
"data": {data goes here...}
},
"Another Inc": {
"admins": ["Dh284HmMK7eTrRL4UgVDh284HntN", ""x1TERNmx1TERNmMK7eTrRL4UgVDh"],
"data": {their data goes here...}
}
}
}
And then I tried to set my rules like this
{
"rules": {
"Respondents": {
"$organisation" : {
".read": "root.child('Respondents').child($organisation).child('admins').val().includes(auth.id)",
".read": "root.child('Respondents').child($organisation).child('admins').val().includes(auth.id)"
}
}
}
}
..but that won't parse in the Firebase Database Rules editor
I get "Error saving rules - Line 7: No such method/property 'includes'", but I need something to match the user id with the array of admins.
Any experience or suggestions?
As you've found, there is no includes() operation in Firebase's security rules. This is because Firebase doesn't actually store the data as an array. If you look in the Firebase Database console or read this blog post you will see that Firebase stores it as a regular object:
"admins": {
"0": "mMK7eTrRL4UgVDh284HntNRETmx1",
"1": "mx1TERNmMK7eTrRL4UgVDh284Hnt"
}
And since that is a regular JavaScript object, there is no contains() method on it.
In general creating arrays are an anti-pattern in the Firebase Database. They're often the wrong data structure and when used are regularly the main cause of scalability problems.
In this case: you're not really looking to store a sequence of UIDs. In fact: the order of the UIDs doesn't matter, and each UID can be meaningfully present in the collection at most once. So instead of an array, you're looking to store set of uids.
To implement a set in Firebase, you use this structure:
"admins": {
"mMK7eTrRL4UgVDh284HntNRETmx1": true,
"mx1TERNmMK7eTrRL4UgVDh284Hnt": true
}
The value doesn't matter much. But since you must have a value to store a key, it is idiomatic to use true.
Now you can test whether a key with the relevant UID exists under admins (instead of checking whether it contains a value):
"root.child('Respondents').child($organisation).child('admins').child(auth.uid).exists()",

Resources