I have put a rule as
(root.child('users').child(auth.uid).child('role/cafe').val() === true)
in my realtime database rules.
My user is
key {
uid: xxxx
name: xxxx
role:{
cafe: true
user: false
}
}
if auth.uid is equal to key, the rule works, however how can I modify the above rule to look for uid inside data
It is unclear what the value of $key is expected to be. So I'm going to assume it's just some random string that isn't used in the rules.
For each security rule, the current data of the node is accessible using the predefined variable data. ".write" and ".validate" rules also have access to the to-be-written data as newData. These are both RuleDataSnapshot objects.
Assuming that a user making changes must be the user given by the uid property, the following rules can be used.
"rules": {
"users": {
"$key": {
".read": "auth != null && data.child('uid').val() == auth.uid",
".write": "auth != null && ((data.exists() && data.child('uid').val() == auth.uid) || (!data.exists() && newData.child('uid').val() == auth.uid)"
}
}
}
The above rules use a fail-fast approach. If a user is not logged in, the check aborts. Otherwise, the user's ID is matched against the existing data at the given node. If the data doesn't yet exist, the newly updated data must also match the current user's ID.
In case the "cafe" role is important, the following rules also require that the "cafe" is set to true to allow read/write operations.
"rules": {
"users": {
"$key": {
".read": "auth != null && data.child('uid').val() == auth.uid && data.child('role/cafe').val() == true",
".write": "auth != null && ((data.exists() && data.child('uid').val() == auth.uid && data.child('role/cafe').val() == true) || (!data.exists() && newData.child('uid').val() == auth.uid && newData.child('role/cafe') == true))"
}
}
}
Note: If $key is storing user info/data, I highly recommend using the user's ID as the key as security rules cannot perform queries like "does userid have admin role?" without structuring your data to allow it. If $key is meant to be a username, instead use a username-to-userID map as it will prevent future problems. One such example is if a user wants to change their username, you can remove and link the new username to the user without ever having to move all their data.
You can use a wild card. In your rules
"users":{
"$key":{
".read" : (root.child('users'+$key).child('role/cafe').val() === true) && (root.child('users'+$key).child('uid').val() === auth.uid ),
".write" : (root.child('users'+$key).child('role/cafe').val() === true ) &&(root.child('users'+$key).child('uid').val() === auth.uid )
}
},
The first condition checks whether the café value for the user is true the second checks whether the uid is the same
Related
I am currently working on a page where all auth users can create a post. The post should also be editable, but only by the creator. Also an admin with a certain email address should be able to edit the post.
This are my security roles:
{
"rules": {
"shoes" : {
".read": true,
"$shoeID" : {
".write" : "root.child('shoes').child($shoeID).child('postowner').val() === auth.uid && auth != null && root.child('users').child(auth.uid).child('uid').val() === auth.uid || auth.token.email === 'admin#admin.com'"
}
},
"websites" : {
".read": true,
"$shoeID" : {
"$storeID" : {
".write" : "root.child('websites').child($shoeID).child($storeID).child('postowner').val() === auth.uid && auth != null && root.child('users').child(auth.uid).child('uid').val() === auth.uid || auth.token.email === 'admin#admin.com'"
}
}
},
"users" : {
"$uid" : {
".read" : "auth != null && root.child('users').child($uid).child('uid').val() === auth.uid"
}
}
}
}
and this is my database structure:
I think I am doing something wrong, because with my current rules any user can change the data.
I'm not sure where the problem comes from, but this rule seems overly complicated to me:
".write" : "
root.child('shoes').child($shoeID).child('postowner').val() === auth.uid
&& auth != null
&& root.child('users').child(auth.uid).child('uid').val() === auth.uid
|| auth.token.email === 'admin#admin.com'
"
Things that can be improved:
There is no need to check auth != null after you've already used auth.uid. Either move the null check first, or skip it altogether.
There is no need to look up the shoe by $shoeID, as it's already in data. So your root.child('shoes').child($shoeID) is equivalent to the (shorter and more idiomatic) data.
The root.child('users').child(auth.uid).child('uid').val() === auth.uid clause seems meaningless in write operations of shoes. This clause should only be enforced in the write operation of /users/$user.
With these changes your rules become:
".write" : "
data.child('postowner').val() === auth.uid
|| auth.token.email === 'admin#admin.com'
"
As said: I don't see what's going wrong in the write operation, but hopefully thee simpler rules make it easier to troubleshoot that problem. I'd recommend starting in the rules playground, and using that to replay what your code does.
I am using firebase database in my app. I have set the rules to allow normal users to create new childs in the node but I found that it's refused.
this is the writing rules of the node :
".write":"auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\" || ((newData.exists() && !data.exists()) || auth.token.email == data.child(\"userEmail\").val())",
I think that newData.exists() && !data.exists() should allow users to write in the database but this doesn't happened
this is the rules of the users node :
"users":{
".write":"auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\" || ((newData.exists() && data.child(\"userEmail\").val() != null) || auth.token.email == data.child(\"userEmail\").val())",
".read": "auth != null"
}
The database strucutre is like that :
-users
-user1
-userName, userEmail ....
-user2
-userName, userEmail .....
when a new user sign up in the app he should be allowed to push his data in the database
this is the database structre :
Ok, I think you're creating extra validation steps that aren't needed.
First
With ".read": "auth != null" on your users root, each user is able to access other user's data, so we should address the access for each user individually.
Second
If you just want to allow users that are authenticated to write and read its own contents, you can remove these extra ((newData.exists() && !data.exists()) and auth.token.email == data.child(\"userEmail\").val()) steps.
Tip: this ((newData.exists() && !data.exists()) comparison means exactly: Write here if you're sending anything but there should be nothing written in this requested "path". You should reflect on the need of this, as I don't know your exact use cases.
Also, I would guess the hardcoded UID you're requesting is of an Admin you've created - I wouldn't recommend this, please read more about user roles on this answer.
To clarify, I think your rules structure should be something like this:
{
"rules": {
".write":"auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\",
".read": "auth.uid == \"DFhNb28506Y345CpJ3Ye7DQNn713\",
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
I have a firebase database rule to block:
non-authenticated users
users trying to create data for some other user
I also want to prevent updating data.I need this code for that:
!data.exists() || !newData.exists()
This is currently how it looks:
"rules": {
"orders": {
"$order":{
".read": "auth != null && auth.token.admin === true",
".write": "auth != null && auth.uid === newData.child('userID').val()" // check the incoming data's userID value here if sender is same. or someone can send orders with other peoples ids. also anyone can send an order with id 123 but only if it doesnt exist. so they cannot update anyones order. is this safe enough?
},
".indexOn": "userID"
}
}
How am I supposed to include this "||" operator in my logical expression for the ".write" rules?
I am not able to delete data. Is it because Im checking the newData's child in the security rules and there is no newData when using ".remove()" in the app? If that is the case, how does write and delete rules in the same line work?
I got a weird issue that I could not use parantheses at first but it is now working. I created an || between two expressions
First expression is for write:
(auth != null && newData.exists() && auth.uid === newData.child('userID').val() && !data.exists())
Here "newData.exists()" in the expression tells us there is an incoming data ( write/update operation) and "!data.exists()" blocks updating data.
Second expression is for deleting rules:
(!newData.exists() && auth != null && auth.uid === data.child('userID').val()
Here "!newData.exists()" says that there is no new data so this is a delete operation, and the next part makes sure everyone can only delete their own data.
So now the code looks like this:
"rules": {
"orders": {
"$order":{
".read": "auth != null && auth.token.admin === true",
".write": "(auth != null && newData.exists() && auth.uid === newData.child('userID').val() && !data.exists()) || (!newData.exists() && auth != null && auth.uid === data.child('userID').val())"
},
".indexOn": "userID"
},
}
Lemme introduce my problem, I would like to build a product like advisory service, that user send their question (post), and then admin give him an advice.
So this is my schema:
About security, I prefer user can get their own questions only, but admin can see all of them.
{
"rules": {
"profiles": {
"$uid": {
".read": "auth != null && (auth.uid == $uid || root.child('profiles').child(auth.uid).child('role').val() == 'admin')",
".write": "auth != null && (auth.uid == $uid || root.child('profiles').child(auth.uid).child('role').val() == 'admin')"
}
},
"posts": {
"$uid": {
".read": "auth != null && (auth.uid == $uid || root.child('profiles').child(auth.uid).child('role').val() == 'admin' || root.child('profiles').child(auth.uid).child('role').val() == 'admin')",
".write": "auth != null && (auth.uid == $uid || root.child('profiles').child(auth.uid).child('role').val() == 'user')"
},
".read": "auth != null && (root.child('profiles').child(auth.uid).child('role').val() == 'admin' || root.child('profiles').child(auth.uid).child('role').val() == 'mod')"
},
".read": false,
".write": false
}
}
From user-view, it looks quite good because I can get quickly posts belong to that user. However, from admin-view, it really bad:
I don't how to purely get all user's posts. I have to get all users, then get posts of user later.
I can not get exactly trigger event by using $watch. When an user add new post, I got "child_changed" event (of user by id), not "child_added" (of post by id)
I'd like to query all questions that not response from admin yet, but dont know how.
A trade-off, I've stored all post in plain array e.g:
posts: {
<post_id>: {
uid: <user_id>,
....
}
}
But then I've got problem with access rule, can not restrict users to access their own data only. Without read-rule, user can bruce-force user-id to get data.
Would you please give me a hint. Thank you in advance!
P/S: I'm working on Firebase w/ angular JS, no backend server
This post describes how to tie multiple accounts to a single $uid in a users collection.
Here's those security rules:
"rules": {
"users": {
"$uid": {
".write": "auth != null &&
(data.val() === null ||
(auth.provider === 'facebook' && auth.id === data.child('facebookUid').val()) ||
(auth.provider === 'twitter' && auth.id === data.child('twitterUid').val()))"
}
},
"usersMap": {
"facebook": {
"$fuid": {
".read": "auth != null && auth.provider === 'facebook' && auth.id === $fuid",
".write": "auth != null &&
(data.val() === null ||
root.child('users').child(data.val()).child('facebookUid').val() == auth.id)"
}
},
"twitter": {
"$tuid": {
".read": "auth != null && auth.provider === 'twitter' && auth.id === $tuid",
".write": "auth != null &&
(data.val() === null ||
root.child('users').child(data.val()).child('twitterUid').val() == auth.id)"
}
}
}
}
Here is how I imagine a practical way to put these rules to use:
A user "Logs in" with their Facebook account.
Does the $fuid exist? If not add a new $uid to users. In the success callback create a $fuid under userMap/facebook with a property value of $fuid.uid equal too $uid.
If it does exist just ignore the request and return a message like "User already exists".
But what if a user wants to tie another account to the same master $uid?
Let's say the user is still logged in with their Facebook account and wants to add their Twitter account. Let's roll through that workflow again...
User logs in with another account.
Does the $tuid exist? No but if the auth object is holding both the Facebook and the Twitter sessions then we don't want to create another $uid - instead we want to map the $tuid to the same $uid the $fuid is mapped too.
Does the auth object have support for accessing properties of simultaneous authentication objects? For example if we were logged in with both Facebook and Twitter auth.id would be different for both right?
Am I thinking about this the wrong way? How is it possible to map additional accounts to $uid using the security rules above?
If you want to tie multiple accounts to a single uid, you'll need to generate your own tokens.
You could, for example, use Firebase.push to generate unique ids for each user, and use those in your tokens. This is no simple matter as it requires that you have:
a way to store the user's ID independent of authentication method (a cookie?)
a way to link auth accounts together when using a diff computer
In many use cases, this could even counter-productive; users will log in with a single auth method in most cases and may even wish to use diff auth methods to create separate accounts. So it may not be worth the trouble unless you have an app that depends on sharing data between providers.