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.
Related
I am trying to figure out Firebase Rules that will allow my users to only see their data in their App.
Currently this is my Realtime Database File:
and this is my Firebase Set Rules:
{
"rules": {
"users": {
"$uid": {
".read": "auth.uid === $uid"
}
},
"jobs": {
"$uid": {
".read": "auth.uid === $uid"
}
}
}
}
How do i change the rules to be able to let users see their data that is relevant to their userid?
It looks like you're using the user's UID as the key for their data in both jobs and users. In that case, you can ensure that each user can only read their own job and profile with:
{
"rules": {
"users": {
"$uid": {
".read": "auth.uid === $uid"
}
},
"jobs": {
"$uid": {
".read": "auth.uid === $uid"
}
}
}
}
Note that this is almost a literal copy of the sample in the Firebase documentation on securing user data.
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.
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.
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",
}
}
}
}