My firebase structure looking like that:
{
"post": {
"uid": {
"text": "Name";
}
},
"games": {
"id": {
"title": "buttons",
"text": "(user id string)"
},
"id": {
"title": "navbars",
"text": "(id string)"
}
},
"guides": {
"1": {
"title": "guide",
"text": "unwriteable string"
}
}
}
(The value doesn't matter..)
I want to allow read and write on everynode, execpt the guides node,
so I tried the following rules:
{
"rules": {
".read": "auth == null",
".write": "auth == null",
"guides": {
".write": false
}
}
}
But. unfortunately, because of the 'father' allowance, firebase doesn't care about the guides specific rule,
Any idea how to achive my goal?
Bacause firebase security rules cascade you can't say someone has permission to write everywhere and later say but not here.
So in you case you would have to add rules for your other paths like this:
{
"rules": {
".read": "auth == null",
"guides": {
".write": false
},
"games": {
".write": "auth == null"
},
"post": {
".write": "auth == null"
}
}
}
As Kato stated this can also be done with the following rule:
{
"rules": {
".read": "auth == null",
".write": "auth == null && !newData.hasChild('guides')"
}
}
The first example will allow you to write only in the games and post nodes whereas the second example will allow you to write everywhere except for the guides node.
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.
I want to create a firebase rule that allows users to read and write to child if a property of that child has a specific value. Let's say my database looks something like this:
"tasks": {
"SDkh7s62jnd23d9": {
"uid": "someuserid",
"other": "datagoes here"
}
}
Here is my current security rule:
"tasks": {
".indexOn": "_state",
"$registerQueueKey": {
".read": "data.child('uid').val() == auth.uid",
".write": "newData.child('uid').val() == auth.uid"
}
}
This rule restricts user write permissions, but it never lets a user read, even if the child they are attempting to read has a uid property that equals auth.id.
I then tested the following rule set:
"tasks": {
".indexOn": "_state",
"$registerQueueKey": {
".read": true,
".write": "newData.child('uid').val() == auth.uid"
}
}
Despite the .read permission being set to a permanent true value, the user still cannot read the $registerQueueKey child.
I then tested the following rule set:
"tasks": {
".indexOn": "_state",
".read": true,
"$registerQueueKey": {
".write": "newData.child('uid').val() == auth.uid"
}
}
Now I can read the child fine, so I attempted this final security rule:
"tasks": {
".indexOn": "_state",
".read": "data.child($registerQueueKey").child('uid').val() == auth.uid",
"$registerQueueKey": {
".write": "newData.child('uid').val() == auth.uid"
}
}
But this rule throws an error because the variable $registerQueueKey is undefined in the scope it is being used.
How do I accomplish a rule like this?
I think the error is because you have not placed a wildcard:
"tasks": {
"$uid": {
".indexOn": "_state",
".read": "data.child($registerQueueKey").child('$uid').val() == auth.uid",
"$registerQueueKey": {
".write": "newData.child('$uid').val() == auth.uid"
}
}
}
Here is the database schema:
Here are the rules:
"notifications": {
"$year": {
".read": "false",
".write": "!data.exists()",
"$month": {
".read": "false",
".write": "!data.exists()",
"$day": {
".read": "false",
".write": "!data.exists()",
"$hour": {
".read": "false",
".write": "!data.exists()",
"$minute": {
".read": "false",
".write": "!data.exists()",
"$data": {
".read": "false",
".write": "!data.exists()"
}
}
}
}
}
}
How can I validate (using ".validate" or ".write" rules) that the users can enter only integers into that tree? Or is there some workaround?
What I am trying to achieve is to create write only (no deletes, or updates) log that has some structure and will be processed later. I can change the structure for example to something like 2015-10-6-17-30 for the key, or something else. I just can't believe that Firebase does not have something for this situation.
Update:
This is not duplicate, I am searching for a workaround, or something else that will help me achieve what I am after.
To validate that a key is a number:
{
"$key": {
".validate": "$key.matches(/^[0-9]+$/)"
}
}
But please read about array-like behaviors in Firebase. Hint: probably use a prefix like "y2015", "m12", etc. to avoid some unexpected results with using numbers.
If using push IDs works for you, here's a security rule structure you could use.
{
"notifications": {
"$notification_id": {
".write": "!data.exists()",
".read": "false",
".validate": "newData.hasChildren(['time', 'state', 'message'])",
"time": {
".validate": "newData.val().matches(/YOUR REGEX/)"
},
"state": {
".validate": ""
},
"message": {
".validate": ""
}
}
}
}
Obviously you'll need to fill in the blanks. The main thing here is that you can use a regex to match the time field.
The actual data would look like:
{
"notifications": {
"-K-z5koYf8mYZu5OfSGR": {
"time": "2015-10-06-17-30",
"state": 1,
"message": "foo"
},
"-K-z5koYf8mYZwgwsfGx": {
"time": "2015-10-06-17-30",
"state": 1,
"message": "bar"
}
}
}
I have a Firebase with a security config like this:
{
"rules": {
"serviceproviders": {
".read": "auth != null",
".write": "auth != null"
},
"bookings": {
".read": "auth != null",
".write": true,
".validate": "newData.hasChildren(['phone', 'time', 'date', 'apikey'])",
"apikey": {
// only allow valid apikey
".validate": "root.child('serviceproviders/' + newData.val()).exists()"
}
},
"status": {
".read": "auth != null",
".write": true
}
}
}
The idea is that users can only post /bookings/ with a valid apikey, that is, an apikey that can be found in /serviceproviders/.
In the Firebase simulator, this works as expected. However, when I use curl from the terminal, or Javascript from a html page, I get error: permission denied back from Firebase. I send exactly the same data (copy & paste).
My curl command looks like this:
$ curl -X POST -d '{"phone":"004512345678", "date":"2014-07-31","time":"10:00","apikey":"AA227D80-122C-4E5D-AEDF-24A829FA6403"}' https://example.firebaseIO.com/bookings/.json
And I get back:
{
"error" : "Permission denied"
}
OK, so after many hours of pulling my hair, I realized that in the guide on Firebase.com, the ".validate" rules are inside a block denoting the ID under that path, thus:
{
"rules": {
"serviceproviders": {
".read": "auth != null",
".write": "auth != null"
},
"bookings": {
".read": "auth != null",
".write": true,
"$bookings_id": {
"apikey": {
// only allow valid apikey
".validate": "root.child('serviceproviders/' + newData.val()).exists()"
},
".validate": "newData.hasChildren(['apikey','date','time','phone'])"
}
},
"status": {
".read": "auth != null",
".write": true
}
}
}
works as expected, because of the "$bookings_id" block.