I use Firebase. Here is what one of my nodes looks like:
--Users
----RandomUID1
----RandomUID2
----RandomUID3
For Rules, this is what I have:
--"users" : {
----".read": "auth != null && !root.child('blockedUsers').hasChild(auth.uid)",
----".write": "auth != null && !root.child('blockedUsers').hasChild(auth.uid)"
--}
What I would like is for reading from the users node to stay the same, but I would like for writing to only be allowed on children of the users node. The reason for this is it would be catastrophic if I accidentally wrote code one night that deleted the entire users node. Is it possible to make a rule so that writing on the node itself is not allowed but writing to child nodes is allowed?
Is there a way to secure myself from such an accident that might occur because of human error?
You can use variables, Firebase variable rules
.At the users node i didn't add a write rule, which doesn't allow for data modification.
"users":{
".read": "auth != null && !root.child('blockedUsers').hasChild(auth.uid)",
"$uid": { //where $uid is child
".read": "auth!==null && auth.uid == $uid",
".write": ""auth!==null && auth.uid === $uid"
}
}
Related
I've stopped to receive emails from Firebase telling me that my realtime database has insecure rules. Here is the beginning of the rules I have set:
{
"rules": {
"aaa":{
".read": "auth != null",
".write": "auth != null",
},
"bbb":{
".read": "auth != null",
".write": "auth != null",
},
//..... rest of the rules.
}
}
Here "aaa" and "bbb" are the nodes I use in my Firebase realtime databse. So you should mention all of yours.
Is this solution suitable?
Your security rules should only allow what your application code does, and nothing more.
So if your code writes directly to aaa and/or bbb and writes whatever data it wants there, then your rules match that.
But typically your code will write data of a specific structure, in which case you should validate that structure with validation rules.
Also: does your code really need to delete or replace the entire aaa and/or bbb node? Or does it only need to append new child nodes to it? Because right now, your rules allow any user to take the configuration from your application and then do firebase.database().ref("aaa").remove() and wipe whatever is under it. Again: if that matches with what your application does, then the rules match that. But... it seem unlikely.
Always try to include User UID for more security.
"aaa":{
"$uid": {
".read": "auth != null && auth.uid === $uid",".write": "auth != null && auth.uid === $uid"
}
}
I have a question for my own sanity. Below is one portion of my firebase rules
{
"rules": {
".read": "auth != null",
".write" : false,
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
"tokens": {
".write": "(newData.val() < data.val())"
}
}
},
...
If I understand correctly the rules state that:
ALL users must be auth'ed in order to read ANY node
ALL user can NOT write to any nodes
Specific to the User node:
In order to read from your own data, you need to be auth'ed and you can only read your own node
In order to write to your own user data, you must be auth'ed and you can only write to your own node
The user/token node can only be decremented and never increased by any user
Can someone confirm my assumptions/understandings reading Firebase security rules documentation.
Also does anyone have any good articles or helpful tips on using the simulator!?
An important concept with the security rules is that read/write rules "cascade" down the tree. This is discussed briefly in the documentation. That means that as you read your rules from top to bottom, the first rule that grants access takes precedence over any rules specified below it on children of that location.
Addressing each of your items:
ALL users must be auth'ed in order to read ANY node (YES)
ALL user can NOT write to any nodes (non-auth'ed users can NOT write to any nodes)
Specific to the User node:
In order to read from your own data, you need to be auth'ed and you can only read your own node (YES)
In order to write to your own user data, you must be auth'ed and you can only write to your own node (YES)
The user/token node can only be decremented and never increased by any user (see below)
In your current rules, the check for smaller token is not effective because the prior rule granting write access to an auth'ed user overrides it. You also need to address the case where there is no existing token value. My suggestion for fixing that is to use a .validate rule. The documentation recommends:
Used once a .write rule has granted access, to ensure that the data
being written conforms to a specific schema.
{
"rules": {
".read": "auth != null",
".write": false,
"users": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid",
"tokens": {
".validate": "!data.exists() || (newData.val() < data.val())"
}
}
}
}
}
As for the Simulator, I don't know of any user guide, but have managed to learn how to use it by experimentation. It's a very effective tool for understand the rules.
Here are a few cases of using the Simulator:
When you open the Simulator, Authenticated is off, which simulates a non-authenticated user. To simulate a read, click on the read button, enter a location: e.g. /users/xyz/tokens, and click on Run. You will see a red X on the lines of the rules that forbid that operation. To simulate an authenticated read, click on the Authenticated button and, for convenience, enter a simple user UID, like "Frank". Now enter location /users/Frank/tokens, click on Run and observe that the read succeeds.
You can do similar tests for writing, entering a location, auth settings and value.
I have this data structure.
root
-foo
-key0
-bar1:baz1
-bar2:baz2
-key1
-bar1:baz1
-bar2:baz2
And I have this rules structure.
"foo":{
".read":true,
".write":"auth != null",
".validate":"newData.hasChildren(['bar1', 'bar2'])"
}
But when I tried to do this firebase.database().ref('/foo').remove() or this firebase.database().ref('/foo').set(null); on the javascript console, the .validate on rules is not being respected and the data on the foo node is being deleted.
Hod do I secure database nodes that are not associated to a certain user?
To prevent /foo from being removed, you can check that newData.val() is not null:
"foo": {
...
".write": "(auth != null) && (newData.val() != null)",
...
}
Validation step is completely skipped because write was allowed.
Note: The .validate rules are only evaluated for non-null values and do not cascade.
Read the following section with great useful video from firebase documentation for securing your data.
https://firebase.google.com/docs/database/security/
You should modify your rules.
"foo":{
".read":true,
".write":"auth != null && newData.exists()",
".validate":"newData.hasChildren(['bar1', 'bar2'])"
}
I've been looking on the docs but I couldn't figure out how to prevent duplicated entries if the email exist on a record. There are my current rules
{
"rules": {
"users": {
"$uid": {
// grants write access to the owner of this user account whose uid must exactly match the key ($uid)
".write": "auth !== null && auth.uid === $uid",
// grants read access to any user who is logged in with an email and password
".read": "auth !== null && auth.provider === 'password'"
}
}
}
}
And my record format is:
Thank you very much
Unfortunately you cannot do this type of query in firebase due to it's distributed nature. In general, arrays are extremely tricky and you can read about their limitations in the context of Firebase here.
The way I see it you have two options, you can index your users "array" by the email itself, or you can keep a completely separate object holding all the emails in the system to check against when you make an insert. My suggestion would be the first, set the user object to users/<email>.
My .validate rules don't seem to get executed when I'm deleting data from Firebase. Is this a bug or intentional behavior?
In general, .validate rules are used to define what the data should look like if it exists. They are intentionally not run when the data is deleted. You should instead use .write rules to make decisions about whether the write (or delete) should be allowed.
For example, consider these rules for securing a simple chat room, where users can create new chat rooms but only delete ones for which they are the owner:
{
"rules": {
".read": true,
"$room": {
".write": "auth != null && (
(!data.exists() && newData.child('owner').val() == auth.uid) ||
(!newData.exists() && data.child('owner').val() == auth.uid))",
".validate": "newData.hasChildren(['owner', 'name'])",
"name": {
".validate": "newData.val().length > 10"
},
"messages": {
".write": "auth != null"
}
}
}
}
The .write rule is used to decide if a user has permission to create / remove a chat room, and the .validate rules are used to define what a chat room should look like if it exists.
If the .validate rules were run on deletes, you could never delete a chat room (since both .validate rules would fail). Hence the decision not to run .validate rules on deletes.