My database looks like the following:
"users": {
"2387653478": {
"score": 1000,
...
},
"8456756547": {
"score": 2000,
...
},
...
}
I want to do a simple ranking sorting by "score" but I have many users and I should add an indexOn in the rules to optimize:
"rules": {
"users": {
"$user": {
".read": "auth != null",
".write": "auth != null && auth.token.firebase.identities['facebook.com'][0] === $user",
".indexOn": ["score"]
}
}
}
Is the optimization 100% functional just when adding the indexOn in rules?
What about the users created before adding the indexOn?
Thanks in advance!
When you update your rules file (either through the Firebase Database console or through the CLI), the Firebase Database (re)creates all the indexes defined in the rules. It does this for all existing data and then continues to update those indexes for any data changes.
But you've defined the index on the wrong level. You want to allow querying of the users node on the individual user's scores. So you have to define the index on users:
"rules": {
"users": {
".read": "auth != null",
".indexOn": ["score"]
"$user": {
".write": "auth != null && auth.token.firebase.identities['facebook.com'][0] === $user",
}
}
}
You'll notice that I also moved your .read rule up to users. Because without that, you would not have read permission on /users and thus can't query it.
Related
Suppose I have a messages node in my database with this structure:
"messages": {
"$messageId": {
"text": "Hello there!",
"created_by": "$userId",
"created_at": 1501749790029
}
}
and this rule:
"messages": {
".read": "auth != null",
"$messageId": {
".write": "auth != null",
// required fields
".validate": "newData.hasChildren(['text', 'created_by', 'created_at'])"
}
}
Seems pretty standard. But my problem is, this structure and rule allows any user to alter the value of created_at to any value, right? The property created_at should be a timestamp of when the message is pushed and should not be editable.
Am I correct if I re-structure my database like this:
"messages": {
"$messageId": {
"text": "Hello there!",
"created_by": "$userId"
}
},
"created_at": {
"$messageId": 1501749790029
}
Basically, I will move created_at to a separate node so it cannot be edited by the user. I will then set up an event trigger via Cloud Functions that will auto-push the timestamp at created_at when a new message is pushed to messages.
The way to do this is to allow setting the created_at value only if there wasn't a previous value, here's the doc
"messages": {
".read": "auth != null",
"$messageId": {
".write": "auth != null",
// required fields
".validate": "newData.hasChildren(['text', 'created_by', 'created_at'])"
"created_at": {
".write": "!data.exists()"
}
}
}
Also, I think you may want to validate the owner of the message to prevent users from posting as other people adding this:
"created_by": {
".validate": "newData.val() == auth.uid"
}
After further researching, I found out I can make use of .validate and the now variable on Firebase rules to prevent invalid timestamps:
"messages": {
".read": "auth != null",
"$messageId": {
".write": "auth != null",
".validate": "newData.child('created_at').val() == now && newData.hasChildren(['text', 'created_by', 'created_at'])"
}
}
This is less complicated than the one I thought of doing. Amazing.
I'm having some issues wrapping my head around the database rules and the documentation isn't helping. I am trying to set things up so that only the user can delete their own items, however at the moment I'm getting permission_denied errors. I am assuming that it is because I don't have a read/write rule on the 'items' level. However I feel that if I just added a 'auth != null' rule it would give to much permission. Any thoughts?
the database setup:
users:
{
user_1 {
items:
{
item_1: true,
item_2: true,
}
},
user_2 {...},
etc {...},
},
items:
{
item_1
{
user: "user_1"
....
},
item_2
{
user: "user_1"
....
},
}
The database rules look like
{
"rules": {
"users": {
"$uid":{
".read": "auth != null && auth.uid==$uid",
".write": "auth != null && auth.uid==$uid"
}
},
"items": {
"$itemID": {
".read": "root.child('Users/'+auth.uid+'/'+$itemID).exists()",
".write": "root.child('Users/'+auth.uid+'/'+$itemID).exists()"
}
}
}
}
At the moment any user can delete any item.
To ensure that only the owner can delete the item, you need to not just verify that:
"items": {
"$itemID": {
".read": "auth.uid == data.child('user').val()",
".write": "auth.uid == data.child('user').val()"
}
}
There is no need to check if they exist in the /users node as far as I can tell, although you can easily add that back if needed.
But if a user can only read/write their own items, I'd model the data differently:
"items": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid"
"$itemID": {
}
}
}
This is much simpler to model and will give you much better scalability, since the database only ever has to consider the data for one user.
The question is about the ships branch expanded below. Everywhere only authenticated users are allowed to write their OWN data everywhere. However, there is one exception. ANY authenticated user may read data out of the ships branch.
So far I had no trouble but here is one special rule:
ANY authenticated user may delete a child under any uid in the ships branch provided that the timestamp is 10 seconds or older.
I want any user to be able to call:
firebase.database().ref('/ships/gp3tJa3tgThukt39EejqJpZq12L2/granit').remove();
uid: gp3tJa3tgThukt39EejqJpZq12L2
shipid: granit
And be granted the rights to delete only.
In order to check the age of the record I store a firebase.database.ServerValue.TIMESTAMP in the perf array at index 0 (I am using an array here because this record is frequently updated and I want to keep data to a minimum).
On the client side, the program can see when a record is likely to have expired and only then call remove. This in order to avoid wasteful failed calls.
I need some help to define the correct rule. The line in question is highlighted in the second example where I attempted to define this rule.
{
"ships": {
"EnBawzb0CjZVgAKrMZD4HE3k5rW2": {
"oasisoftheseas": {
"param": {
"scale": 0.33075936163570846,
"type": "cruise/royalcaribbean/oasisoftheseas"
},
"perf": {
"0": 1,
"1": 1.11014724E7,
"2": 1.70473256E7,
"3": 115.7,
"timeStamp": 1475144447302
}
}
},
"gp3tJa3tgThukt39EejqJpZq12L2": {
"granit": {
"param": {
"scale": 0.12235531736978282,
"type": "riverbarge/granit"
},
"perf": {
"0": 5,
"1": 2.05622392E7,
"2": 13154087,
"3": 285.9,
"timeStamp": 1475144450086
}
}
}
}
}
Below the rules. It is the write rule for $shipid that I am interested in defining correctly.
{
"rules": {
"anchors": {
"$uid":{
".read": "auth.uid === $uid",
".write": "auth.uid === $uid"
}
},
"completed": {
"$uid":{
".read": "auth.uid === $uid",
".write": "auth.uid == $uid"
}
},
"ships": {
".read": "auth !== null",
"$uid":{
".write": "auth.uid === $uid",
"$shipid":{
".write": "((auth !== null) &&
(now - data.child('perf').child('timeStamp').val() >= 10000))"
}
}
},
"shipslog": {
"$uid":{
".read": "auth.uid === $uid",
".write": "auth.uid === $uid"
}
}
}
}
Well, I figured it out as confirmed by Frank. The solution edited in the question is correct, secure and working.
"ships": {
".read": "auth !== null",
"$uid":{
".write": "auth.uid === $uid",
"$shipid":{
".write": "((auth !== null) &&
(now - data.child('perf').child('timeStamp').val() >= 10000))"
}
}
},...
Any data can be written to the ships branch by the owner of the record. At a deeper level I use the uid wildcard and shipid wildcard to get to the data that can expire and needs to be removed.
The timestamp is secure as it is provided by the server on the server side. Deleting a ship can only happen by any authenticated user AND provided the difference between now and the timestamp is greater then 10000 milliseconds.
This is probably a nice clean generic way to let client apps cleanup old data without the need for serverside business logic.
I'm trying to set users to get only own objects. I have a tasks which have 3 attributes on fb-database: task-text, datetime, user. user have user uid. I want to write a rule that allow user to read only own tasks.
So far I have this and it dont work:
{
"rules": {
"tasks": {
".read": "auth.uid == data.child('user').val()",
".write": "auth.uid != null",
}
}
}
You'll want to break up the tasks by user, so you'll have a structure that looks like:
root
tasks
Puf
...
Andrew
...
James
...
This can be secured via rules that look like:
{
"rules": {
"tasks": {
"$userId": {
".read": "auth.uid == $userId",
".write": "auth.uid == $userId",
}
}
}
}
this is my problem :
WARN: FIREBASE WARNING: Using an unspecified index. Consider adding ".indexOn": "id_logement" at /images/-KNx2y3mAkJZ-Poa7R03 to your security rules for better performance
I'm not really a master in firebase and rules so it doesn't really mean something to me ... but i know there is an issue !
This is my data structure :
And this are my rules !
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"accounts" : {
".read": true,
".write": true,
".indexOn": ["userId", "email", "lat",]
},
"geofire" : {
".read": true,
".write": true,
".indexOn": ["g"]
},
"logements" : {
".read": true,
".write": true,
".indexOn": ["id_account"]
}
}
}
I think it's because i don't know how to write this Unique id in rules ! Can you please help me for this ? Thank you
Answering your question what you are looking for to use in your rules is the $id notation that is a wildcard to represent a branch key.
{
"rules": {
".read": "auth != null",
".write": "auth != null",
...
"images": {
"$imageId1": {
".indexOn": "id_logement"
}
}
}
}
But, keep in mind that you should not be storing your imagesUrl inside a two level deep keys. Work your code to have images/imageUniqueKey/imageData structure instead of images/imageUniqueKey1/imageUniqueKey2/imageData. Then you would have your rules as bellow.
"images": {
".indexOn": "id_logement"
}