Firebase Security Rules: Unknown variable $requestId2? - firebase

When user1 --> user2 (user1 sends a request to user2). I am trying to perform certain checks using firebase rules, which are:
Check if username2 exists
Check if both users are not already friends
for that I've written the following rule:
{
"rules":
{
"requests":
{
"$requestId":
{
".read": "auth != null",
".write": "auth!=null &&
data.child('username2').val() == true &&
root.child('usernames-list').child( data.child('username2') ).exists() &&
!root.child('user-requests').child( auth.uId ).child('accepted-pending').child($requestId2).child( data.child('username2') ).exists()"
}
}
}
}
But it returns the following errors at two areas:
data.child('username2') ----> Line 9: child() expects a string argument.
data.child($requestId2) ----> Line 9: Unknown variable $requestId2
Data Structure:
requests
---$requestId
---key: value
user-requests
---$userId
---pending
---$requestId
---key: value
---accepted
---$requestId
---key: value
---rejected
---$requestId
---key: value
---unfriend
---$requestId
---key: value
---accepted-pending
---$requestId
---key: value
Incoming json:
user1 --> authId of user1
username1 --> username of 1st user
username2 --> username of 2nd user
...
Note 1: I've tried using newData. instead of data. and still get the same errors. Also, newData('username2').isString() also returns the same error
Note 2: I could avoid the $requestId2 (error) by making a separate list only containing the usernames that user1 is friends with and compare it against that; but I'd still need to be able to use data.('username2') as a field withing the root.child()
Edit 1: The following rule publishes successfully, however I'm not sure if it still does what I've mentioned (need to check) :
".write": "auth!=null &&
newData.child('username2').val() == true &&
root.child('usernames-list').hasChild( newData.child('username2').val() ) &&
!root.child('user-requests').child( auth.uId ).child('accepted-pending-usernames').hasChild( data.child('username2').val() )"
Ref:Firebase security - newData() as a parameter of hasChildren() expression

Edit 2: This should be the solution:
.val() has been added within the brackets
.exists() remains at the end
I've created a separate list of usernames (/accepted-pending-usernames/) to overcome the wildcard error
data has been replaced with newData
".write": "auth!=null &&
newData.child('username2').val() == true &&
root.child('usernames-list').child( newData.child('username2').val() ).exists() &&
!root.child('user-requests').child( auth.uId ).child('accepted-pending').child('accepted-pending-usernames').child( data.child('username2').val() ).exists()"
Note: Question may still remain unsolved as its unclear if firebase allows one to use multiple wildcards in the rules

Related

firebase rule to make sure a owner of comment has access to read, write and any other user to only like it. number_check is the user table

The following is the database structure and i want to make sure the owner of the comment whose user_id is part of the comment object to have read and write access to the comment and all other users have read access to comment and the ability to like it to increase like_count:
The following is the security rule I came up with:
{
"rules": {
"comments": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().child('users').child(auth.uid).val() != null && newData.parent().child('comments').hasChildren().hasChildren().child('user_id').val() == auth.uid)",
"$commentId": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().parent().child('users').child(auth.uid).val() != null && !newData.parent().parent().child('comments').hasChildren().hasChildren().child('like_count'))"
}
}
}
}
So, for read better will be:
".read": "root.child('users').hasChild(auth.uid)"
".write" is more complicated:
You need to only allow edit in like_count, and you need to allow for only one like per user
Proper way to do this will be expand structure of your "post", something like like_list, here or in other place to protect data transfer from growing when post will be download by client
So in "comment" node you want to allow every auth user to .write and .read, they will be able to add new comment then (upper rules will be fine for this).
For $commentId node rules will look like:
".write":".data.child('user_id').val() == auth.uid"
You can link like_list to a clinet by adding user_id to the "like / $ commentId" node and setting listener in firebase funtions for this node. adding new user_id will fire "write" event and then call function to secure increase the value of like_count.
You can archive it with a firebase-function and transaction.
https://www.tutorialspoint.com/firebase/firebase_write_transactional_data.htm
If you really don`t want to change schema then you need to secure every single child of comment, like:
"$commentID":{
"comment":{ ".write":"".data.parent().child('user_id').val() == auth.uid" ),
...//things allowed to edit by owner
"like_count":{
".write":"root.child('users').hasChild(auth.uid)",
".validate":"newData.val() == data.val() + 1"
}
}
But this will not secure post from giving more than one like per user.

Firebase database rules. How to secure a node from being deleted with .set(null) call

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'])"
}

Which rule allows me to prevent duplicate insert by email/id on firebase?

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>.

How could I reach a data property with .validate rules?

I trying to verify if an user profile has an specific property in order to allow .write data in a path of my Firebase, but I haven't found the way.
This is the user profiles structure in my Firebase:
root
|--user_profiles
| |--uid
| | |--name
| | |--email
| | |--invite // How could I reach the properties of this path with my .validate rules?
And these are the rules I'm trying to make it work:
"invitations": {
".write": true,
".validate": "auth.provider == 'provider' && auth.id == '123456'",
"$invitation": {
".read": true,
"used": {
".validate": "root.child('user_profiles').hasChild(auth.uid).(...)" // I'm stuck here.
}
}
}
The idea is to allow .write in "used" if, and only if, the property "invite" match with my requirements (Whether they are equal or not, for example).
I'm really stuck in this and I don't want to make it works without understand it.
You can simply concatenate the paths to the invite:
".validate": "root.child('user_profiles/'+auth.uid+'/invite').val() === ???"
Replacing ??? with your criterium, such as data.val() (for the current value at this path), or newData.val() (for the value being set at this path).

How to restrict access to data that are not assigned to a specific user?

Let's take the example of http://up2f.co/15euYdT where one can secure the firebase app by checking that only the creator of a comment can change a comment.
Let's assume that we need to keep in another structure the total number of comments, something like
"stats" :{
"comments":{
"count":2
},
}
We need to protect this part from direct access from registered users.We could do something like
"stats" :{
"$adminid":{
"comments":{
"count":2
},
},
}
where we could only allow an admin to have access there.
To do this we would need to create a persistent connection to Firebase that would listen to changes in the comments table and would trigger an event to update the stats table.
Is this possible? If not how else can we secure data that is not assigned to a specific user?
Since your admin process will use a secret token to log in, security rules will not apply. Thus, you can simply secure client access using:
// not applied to privileged server logging in with token
".write": false,
If, alternately, you wanted clients to increment the amount, you could use the following trick, which only allows them to increment the counter, and only allows them to add a comment if the counter has been updated. (See a working demo http://jsfiddle.net/katowulf/5ESSp/)
{
"rules": {
".read": true,
".write": false,
"incid": {
"counter": {
// this counter is set using a transaction and can only be incremented by 1
".write": "newData.isNumber() && ((!data.exists() && newData.val() === 1) || newData.val() === data.val()+1)"
},
"records": {
"$id": {
// this rule allows adds but no deletes or updates
// the id must inherently be in the format rec# where # is the current value of incid/counter
// thus, to add a record, you first create a transaction to update the counter, and then use that counter here
// the value must be a string less than 1000 characters
".write": "$id >= 'rec'+root.child('incid/counter').val() && !data.exists() && newData.isString() && newData.val().length <= 1000"
}
}
}
}
}

Resources