Firebase Database Rules Using an unspecified index - firebase

I am struggling with what seems to be a very trivial task. I am receiving the following warning from Firebase when doing a simple query:
Query:
admin.database().ref('/dispatch/')
.orderByChild('shipmentKey')
.equalTo(shipmentKey)
.once('value')
.then(
Warning:
FIREBASE WARNING: Using an unspecified index. Consider adding ".indexOn": "shipmentKey" at /dispatch to your security rules for better performance
My database looks like the following:
And my database.rules.json looks like this:
{
"rules": {
"dispatch": {
".indexOn": "shipmentKey"
}
}
}
and have also tried:
{
"rules": {
"dispatch": {
".indexOn": ["shipmentKey"]
}
}
}
I have already read all the other stackoverflow questions on this topic and cannot seem to rid our project of this warning, any help would be greatly appreciated.
UPDATE 06/22/2017
I have solved the problem, and it was very simple. It turns out that firebase deploy wasn't actually deploying the database rules so the database.rules.json was not being uploaded. I simply ran the following command:
firebase deploy --only database
And everything works great now! Thanks to Bob Snyder for helping point this out. Hope this helps someone else out there!

You need to put all the indexes in square brackets, separated by a comma if you have more than one.
{
"rules": {
"dispatch": {
".indexOn": ["shipmentKey"]
}
}
}

Related

Index not defined in firebase

This is the error I receive:
"error" : "Index not defined, add \".indexOn\": \"release/date\", for path \"/north_america\", to the rules"
And this is the structure of my firebase data:
And my added rules don't seem to work, any help?
Try updating your database rules again with the index another level down.
"likes": {
"north_america": {
"$someid": {
"release": {
".indexOn": ["date"]
}
}
}
Also you should be able to wildcard out north_america so it works globally.

In react native app (through Expo) using firestore permissions - request.auth is always null

Here's my code:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log("We are authenticated now!");
firebase.firestore().collection("users")
.doc(firebase.auth().currentUser.uid).set({ name: "new name" });
} else {
loginWithFacebook();
}
});
And, here are my permission rules:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null;
}
}
}
It seems like no matter what, request.auth is always null.
The error I get:
Firestore (4.8.0) 2017-12-20T04:18:27.321Z [Connection]: WebChannel
received error: {"code":403,"message":"Missing or insufficient
permissions.","status":"PERMISSION_DENIED"}
I downgraded to Firebase SDK 4.6.2 when I had this problem with latest Firebase JS SDK on Expo.
Also, you have to do this in order for everything to work:
Copy https://gist.githubusercontent.com/mikelehen/444950a35019208f3aaf18d37ab4937c/raw/85ac4cb3108d92163cb8f04fbdddcc88d4081aab/index.js
over your node_modules/#firebase/webchannel-wrapper/dist/index.js
Without this, you'll get nothing from your collections.
Hope this helps.
I diffed the requests coming from the same code running in the browser vs expo. One difference I noticed was missing Origin header. I patched the xhr and it works for my uses. I'm not sure if this is a bug or expected behavior. I've asked on slack/firestore and discord/react-native but didn't get much input.
const xhrInterceptor = require('react-native/Libraries/Network/XHRInterceptor')
xhrInterceptor.setSendCallback((data, xhr) => {
if (xhr._method === 'POST') {
// WHATWG specifies opaque origin as anything other than a uri tuple. It serializes to the string 'null'.
// https://html.spec.whatwg.org/multipage/origin.html
xhr.setRequestHeader('Origin', 'null')
}
})
xhrInterceptor.enableInterception()
Seems like a bug - as other high-reputation users experienced it too. I'll update more when we find a fix. If anyone got it working (on react-native through expo) please share.
Sorry I'm late to the party, but I just came across this and thought I'd share my experience. I had the exact same issue, and after months of researching and debugging, it resolved itself when I upgraded to firebase v 5. Here's a link to my SO post and solution. Hope it helps:
https://stackoverflow.com/a/51115385/6158840
I fixed that issue by downgrading Firebase version to 5.0.0 (I was using version 6.4). Hope it could help others..
In your package.json
dependencies: {
"firebase": "^5.0.0"
}
update dependencies
npm install
First check the Stuff on the FIREBSAE Rule Exectuion Simulator,
and secondly your issue sounds like you'r requests are not in Sync,
check your rules (if possible) in the Web view , as i tried doing the same and it worked for me
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Please log your UID and confirm it is not null. Then modify the permission rule as -
// Grants a user access to a document matching their Auth user Id
service cloud.firestore {
match /databases/{database}/documents {
// Collection named "users", document named after the userId
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
}
}

Restrict querying by a certain child value in security rules

I'm struggling to come up with the best way to structure part of my database and its associated security rules.
I have chat groups, and users can be added to those groups at any point. When users are added to a group, they should be able to retrieve only the messages sent after that. It shouldn't be possible for them to retrieve any messages that were sent before they (the users) were added to the group.
My first approach wrongly assumed that security rules would apply only to the data being queried.
Simplifying it for this question, I had the following structure:
{
"groups": {
"-Kb9fw20GqapLm_b8JNE": {
"name": "Cool people"
}
},
"groupUsers": {
"-Kb9fw20GqapLm_b8JNE": {
"3JzxHLv4b6TcUBvFL64Tyt8dTXJ2": {
"timeAdded": 1230779183745
},
"S2GMKFPOhVhzZL7q4xAVFIHTmRC3": {
"timeAdded": 1480113719485
}
}
},
"groupMessages": {
"-Kb9fw20GqapLm_b8JNE": {
"-KbKWHv4J4XN22aLMzVa": {
"from": "3JzxHLv4b6TcUBvFL64Tyt8dTXJ2",
"text": "Hello",
"timeSent": "1358491277463"
},
"-KfHxtwef6_S9C5huGLI": {
"from": "S2GMKFPOhVhzZL7q4xAVFIHTmRC3",
"text": "Goodbye",
"timeSent": "1493948817230"
}
}
}
}
And these security rules:
{
"rules": {
"groupMessages": {
".indexOn": "timeSent",
"$groupKey": {
".read": "root.child('groupUsers').child(auth.uid).child($groupKey).child('timeAdded').val() <= data.child('timeSent').val()"
".write": "!data.exists() && root.child('groupUsers').child(auth.uid).child($groupKey).exists() && newData.child('from').val() === auth.uid",
}
}
}
}
With that, I figured I could retrieve the messages for a particular group like so:
var myTimeAdded = /* already retrieved from the database */;
firebase.database()
.ref('groupMessages/-Kb9fw20GqapLm_b8JNE')
.orderByChild('timeSent')
.startAt(myTimeAdded)
.on('child_added', /* ... */);
But like I said, that was a wrong assumption. Any suggestion on how I could achieve this?
Read rules are enforced at the location where you attach a listener.
So in your case that is groupMessages/-Kb9fw20GqapLm_b8JNE. If your user has read permission there the listener is allowed. If the user does not have read permission, the listener is rejected/cancelled.
This means that rules cannot be used to filter data. We often refer to this as "rules are not filters" and it's one of the most common pitfalls for developers who are new to Firebase's security model. See:
the section rules are not filters in the Firebase documentation
previous questions about Firebase that mention "rules are not filters"
By themselves your rules are not wrong: they only allow access to each specific child if it's not too old. They just don't allow you to run a query on groupMessages/-Kb9fw20GqapLm_b8JNE anymore.
The common way to work around this is to have a separate structure (commonly called an "index") with the keys of the items that your query would otherwise return. In your case it looks like that might turn into a index for each user with the keys of all messages after they joined.
But I'll be honest, it sounds like you're trying to use security rules in a SQL way here. It seems unlikely that the user isn't allowed to see older messages. More likely is that you don't want the user to be bother by the older messages. In that case, I'd just solve it with a query (as you already have) and remove the ".read" rule.

Timestamp calculations at Firebase server

Is it possible, and recommended, to define write rules at Firebase server which needs to consider calculations on timestamps?
Example
Scenario: User is trying to add new Comment to existing Thread.
Write Rule: Comments can only be added to existing Thread if current time is between Thread.openAtTimestamp and Thread.closesAtTimestamp.
This would be fairly easy to solve with use of momentjs, for instance. But I guess that momentjs lib is not available in Firebase rules?
Say you have this data structure:
threads
$threadid
openAtTimestamp: 1453820367233
closesAtTimestamp: 1454425139712
comments
$threadid
$commentid
author: "Ismar Slomic"
timestamp: 1454425139711
Then you can only have a comment in a thread if its timestamp is between the openAtTimestamp and closesAtTimestamp of that thread.
{
"rules": {
"comments: {
"$threadid": {
"$commentid": {
"timestamp: {
".validate": "
newData.val() > root.child('threads').child($threadid).child('openAtTimestamp') &&
newData.val() < root.child('threads').child($threadid).child('closesAtTimestamp')
}
}
}
}
}
This is just a rough outline to get you started. The Firebase documentation on security rules has tons more information.

Firebase Security API - Complex Data Structure - How to enforce relationships?

For the past few weeks i've been exploring Firebase and its features to build a web app, but I've kind of ran into a wall when it comes to security rules.
I've build a data structure on Firebase but I'm not sure if it follows best practices (if it doesn't, feel free to suggest anything different about it):
{
"groups" : {
<GROUP_KEY>
"name": "",
"rels": {
"users": {
<RELS_USERS_KEY>
"key":"" (USER_KEY)
},
"notes": {
<RELS_NOTES_KEY>
"key":"" (NOTE_KEY)
}
},
"isPrivate": true
},
"users": {
<USER_KEY>
"email": "",
"rels": {
"friends": {
<RELS_FRIENDS_KEY>
"key":"" (USER_KEY)
}
},
},
"notes": {
<NOTE_KEY>
"title": "",
"description": "",
"rels": {
"files": {
<RELS_FILES_KEY>
"key":"" (FILE_KEY)
}
}
},
"files": {
<FILE_KEY>
"mode": "",
"url": ""
}
}
The application flow is as follows:
The user signs up: a key is created on "users";
Is redirected to "Groups" view, where he should be shown only
groups that have his ID in RELS > USERS, or that has
"isPrivate":"false";
As the user creates a Group, a new group is added with his ID in RELS > USERS;
Entering the Group view, he should only see notes that are in RELS > NOTES for that group.
The rest of the logic follows the same principle, and I believe that if I can get through the first hurdle of understanding the Firebase security rules and applying them to this case, I can get through the rest.
I've tried a couple of rules, but I can't seem to get any feedback at all from the web application, debugging this has been a trial-and-error process, and its not really working.
Could someone help me at least understanding the logic behind it ? I've read all of their tutorials but they all seem very shallow with no deeper examples on complex structures.
Thanks for the help.
EDIT
I've added the debug:true flag to the login (thanks #Kato), but I'm still getting no feedback on the rules. With the rules as below, I still enter the "Groups" view, but get no feedback on the console, and the logged-in user sees groups he shouldn't:
{
"rules": {
"groups": {
".read": "data.child('rels').child('users/' + auth.user).exists()",
".write": "data.child('rels').child('users/' + auth.user).exists()"
}
}
}
As for the rules I've tried, they were countless, but this is the most recent one (still no feedback).
Maybe I'm missing something ?
Thanks again.
Rules cascade. That is, if any rule allows read, then you cannot revoke it later in a nested child. In this way, you can write rules like the following:
"$record": {
// I can write the entire record if I own it
".write": "data.child('owner').val() === auth.uid",
"foo": {
// anybody in my friends list can write to foo, but not anything else in $record
".write": "data.parent().child('friends/'+auth.uid).exists()"
},
"bar": {
// this is superfluous as permissions are only "granted" and never "revoked" by a child
".write": false
}
}
Note how, because I am the owner, I can also write to foo and to bar, even though bar has tried to revoke my read privilege.
So in your case above, your rules declaration lists read: true which allows full read access to the entire repo. Change that to false and you'll see better results.

Resources