Preventing child nodes from being removed in Firebase - firebase

I am running into a painful issue with Firebase security.
I would like an authenticated user to create children under a child node however not be allowed to delete any of the children.
Please see comments in 'used' node
security rules below:
"users": {
"$userid":{
".read": "$userid === auth.uid",
".write":" $userid === auth.uid && newData.exists()",
//writeable by user
"qrcodevalue":{},
"datesubscribed":{},
//not writeable by user
"confirmed":{".validate":false},
"issubscribed":{".validate":false},
"periodend":{".validate":false},
"stripeid":{".validate":false},
"stripesubscription":{".validate":false},
"subscriptionstatus":{".validate":false},
//user should be able to create children under this node but not delete
"used":{
"$promotionid":{
"dateused":{}
}
},
}
},
Any help would be greatly appreciated.

From the Firebase security documentation on new and existing data:
The predefined data variable is used to refer to the data before a write operation takes place. Conversely, the newData variable contains the new data that will exist if the write operation is successful. newData represents the merged result of the new data being written and existing data.
To illustrate, consider a rule that would allow us to create new records or delete existing ones, as long as data does not already exist at a given path, but not to make changes to the data:
// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"
So for you that would translate to something like this:
//user should be able to create children under this node but not delete
"used":{
"$promotionid":{
"dateused":{
".write": "newData.exists()"
}
}
},
This allows the user to write any data to the node, but not delete it.
If you want them to only create but not change the data, it becomes:
".write": "!data.exists() && newData.exists()"

Related

How to write Rules condition based on input data in Firebase Realtime Database

I have a Firebase Realtime Database which gets written to in a predicted structure.
Is there a way to verify using rules that the authenticated user's UID matches one of the data input values?
I know it is possible to validate this using values from the tree structure, but in my case I want to compare the UID to data in the input JSON.
For example, in my case, the input data is in the following JSON data structure:
{ "UserID": "SOME_UID", "UserName": "SOME_USERNAME", "CompletionMoves": 3, "CompletionTime": 12.345 }
And it is posted to a location which includes a level name and then the user's UID, akin to /Level3/SOME_UID.
I want to verify the user's UID equals the value of UserID in the data.
I thought of trying the following rule logic, but it fails on tests for some reason on the uid comparison line:
{
"rules": {
"$level": {
"$uid" : {
".read": "auth != null",
".write": "auth != null
&& newData.child($level).child($uid).child('UserID').val() == auth.uid",
}
}
}
}
Does anybody know what I am missing? Thanks in advance!
The newData and data variables in security are snapshots of the (new) data at the node where the rule is declared. So you don't need to build the entire path to UserID, and can instead refer to it directly.
So instead of:
&& newData.child($level).child($uid).child('UserID').val() == auth.uid",
Use:
&& newData.child('UserID').val() == auth.uid",
Instead of '.child($uid)', try '.child(auth.uid)'.
This works for me (if I've understood your question correctly).

firebase rule - retrieve only items where child has certain value

Is there a way to add a firebase security rule that prevents certain items in a collection from being read based on a value within each child item?
My example:
JSON:
orders{
orderA: {
name: x,
company:a
isDeleted: true
}
orderB: {
name: y,
company:a
isDeleted: false
}
}
It would be great to restrict users to be only able to read all orders where isDeleted === false
My Rule as I currently have (NOT WORKING):
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null && data.child('isDeleted').val() === false",
"$ord": {
".write": etc
}
},...
The above doesnt work because "data" doesnt represent the right object - I can only use data inside the "$res" area.
If I remove "&& data.child('isDeleted').val() === false" it works but of course brings back both records.
My request is something like this, so the $res doesn't apply - as I'm getting ALL orders by companyId
http://mysite.firebase.io/orders?auth="xyz"&orderBy="companyId"&equalTo="a"
Is it even possible for a "retrieve all" type REST call like this and to filter out certain values via the firebase security rules? Am I just as well to retrieve all and then filter them out once I get them back in the front end??
Firebase's server-side security rules don't filter data. I highly recommend checking out the documentation, and some previous questions on this topic, as it's a very common misconception.
Instead the rules merely ensure that any read (in this case) operation, adhere to your requirements. So for your ".read": "auth !== null && data.child('isDeleted').val() === false", rule that means that the server checks if the user is logged in (they are), and that the node they are reading has a child isDeleted that is false. And since /orders/isDeleted does not exist, the read gets rejected.
You can securely allow access to only undeleted data by combining a query that only selects undeleted nodes with security rules that validate this query. Based on the example in the documentation on query based rules that'd look something like:
"rules": {
"orders": {
".indexOn": "companyId",
".read": "auth !== null &&
query.orderByChild == 'isDeleted' &&
query.equalTo == false"
}
}
This will work to get only non-deleted nodes. But since you can only order/filter on one property, you can't then also filter on companyId. You could allow that by introducing a synthesized isDeleted_companyId property, as shown in my answer here: Query based on multiple where clauses in Firebase

Firebase database rules, differentiate between create and update

I want to differentiate somehow between creating or updating a list with write rules. Any user can create a new chat, while only a user inside that chat should be able to update it. So basically, I want to have an update rule that checks in another denormalized list if that user is inside that chat before being able to update (similar if not equal to the read rule which works fine) without breaking the auth != null rule for create new chat.
"chats": {
"$chat": {
".write": "auth != null",
".read": "root.child('chats_by_user').child(auth.uid).child($chat).exists()"
}
}
Is there a way to do this?
An update usually means that newData will be different from existing data.
So the rule to only let the user update if he is inside the chat would be:
"newData.val() != data.val() && root.child('chats_by_user').child(auth.uid).child($chat).exists()"
And a create means that there is currently no data under that node.
So the rule to only allow create operations would be:
"!data.exists()"
Now putting it all together:
"chats":{
"$chat":{
".write":"auth!=null && ((newData.val() != data.val() && root.child('chats_by_user').child(auth.uid).child($chat).exists()) || !data.exists() )",
".read":"root.child('chats_by_user').child(auth.uid).child($chat).exists()"
}
}

Grant access to a user only if the node contains their uid?

So, I have an app that has a messaging feature, where two users can message each other. I am structuring my data in the "messages" node, where each node are the message threads between two users. Each node is named after the two uid's of the two users who are communicating, sorted alphabetically.
For example,
if user (dd77gg) and user (zz22ss) are in a conversation, the node would be named "dd77ggzz22ss". I know you can grant access in Security Rules by doing
{
"rules": {
"messages": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
But, in my case, the nodes are not simply named $uid, but rather two uid's merged together. So, my question is, how would I simply grant access, only if the current user's uid is found somewhere in the node name?
First of all do not sort your uids alphabetically, this may seem like a good idea but it is not deterministic. For example. Say the two uids were 'cat' and 'bca'. Sorting them alphabetically would give you 'aabcct'. Now suppose you have another two uids: 'cat' and 'cba'. If you were to sort them you will get the same concatenation of uids, you will get aabbcct. This will cause previous conversation data to be overwritten. Concatenating two uids is actually the best way to go. You do not need to sort them, you just have to have a determenistic way of concatenating them. Ie: who's uid should I place first.
Now to answer your question you could just use the contains method in the firebase rule.
{
"rules": {
"messages": {
"$uid": {
".read": "$uid.contains(auth.uid),
".write": "$uid.contains(auth.uid)"
}
}
}
}
You just check if it contains one of the users id, if it does then they can read it of course. I hope this helps.

Unable to add new 'user' row to Firebase Users collecton using BackFire Collection

I'm using Backfire Collection trying to add a new user row to a custom "Users" collection in Firebase, after the user has registered successfully using FirebaseSimpleLogin.
Here are my security rules:
"users": {
"$user_id": {
".read": "auth.id === $user_id",
".write": "(!data.exists() && (newData.child('id').val() + '' === auth.id)) || ($user_id === auth.id)"
}
The security rule should accomplish the following:
Allow a newly registered user to create a new 'User' row, so his personal information is stored under the URL "http://myurl.firebase.com/users/1/".
A logged-in user can only read his own user row.
A logged-in user can only update his own user row.
However, I'm getting permission denied using the following code:
var FireUsers = Backbone.Firebase.Collection.extend({
model : Backbone.Model,
firebase: new Firebase(FirebaseURL + '/users/')
}),
fireUsers = new FireUsers();
fireUsers.add(newUserObj);
I tried it in the simulator and the only way I'm able to satisfied the above 3 conditions is to set the security rule to "auth != null", which is obviously not ideal.
Any help is appreciated!
-Tony
If a logged-in user can only read their row in the users table, then you cannot create a collection for the entire users collection (since that would mean that everyone could read the users table). Creating a new instance of Backbone.Firebase.Collection for a particular URL will immediately try to download all the data at that URL, which will fail because of the security rules.
I recommend using Backbone.Firebase.Model instead, and creating a new instance for the user, directly at the URL with the user ID included.

Resources