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"
},
}
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 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
I'm looking for some advice or a possible solution with tightening up my firebase rules.
This is my user tree in Firebase:
users
|_male
|_uid
|_female
|_uid
The UID will be an epoch timestamp when the account is created which will be a signed integer.
These are the firebase rules which basically ensures the user has logged in and authenticated with Facebook before they can read or write to users:
"users": {
"male":{
".indexOn": ["uid"]
},
"female":{
".indexOn": ["uid"]
},
".read": "auth != null && auth.provider === 'facebook'",
".write": "auth != null && auth.provider === 'facebook'"
},
I only want users to read/write to their tree, for example:
users->male->uid->1233254...
I'm afraid with my rules above, they could potentially read and write from/to another users tree.
It would be great if I could compare the app UID with the Facebook UID.
I do capture this detail in another tree on the database e.g:
user_fbuid
|_fbuid
|_facebook:a1244dxs
|_uid
I do have better rules here that check against auth.uid:
"user_fbuid": {
"fbuid":{
"$fbuid": {
".indexOn": ["fbuid"],
".read": "$fbuid === auth.uid && auth.provider === 'facebook'",
".write": "$fbuid === auth.uid && auth.provider === 'facebook'"
}
},
},
If anyone has any ideas, I'd love to hear. Thanks
I ended up using the facebook id attribute as my own uid and the rules below:
"$uid": {
// only the user can read and write to their tree
".read": "auth != null && auth.provider === 'facebook' && auth.token.firebase.identities['facebook.com'][0] === $uid",
".write": "auth != null && auth.provider === 'facebook' && auth.token.firebase.identities['facebook.com'][0] === $uid"
},
The JSON looks like this:
I want to remove the child which is [steven : uid] from user_lookup
This is the code:
self.ref.child("user_lookup").queryOrderedByValue().queryEqual(toValue: user?.uid).observe(.value, with: { snapshot in
if snapshot.exists() {
print("I got it")
// remove [steven, uid]
} else {
print("Not found")
}
I don't know how to remove the child, but i've tried to type removeValue but i got Permission_denied
Here is the rules:
`"user_lookup": {
".read": "auth !== null",
".write": "auth !== null && !data.exists()",
".indexOn": [".value"],
I think its because "!data.exists()" but I don't want to duplicate data, so how to fix that?
And how can i remove the child, Any help will be appreciated.
I think this might be what you want:
".write": "
(auth.uid === newData.val() && !data.exists()) ||
(auth.uid === data.val() && !newData.exists()"
The first expressions or the or allows user to claim their name if it doesn't exist yet. The second expression allows then to delete their claim.
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