Firebase database rules, differentiate between create and update - firebase

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()"
}
}

Related

Using Security Rules in Firebase to Limit Access in a Social Media App

I am implementing a social media app where people make posts and others can upvote/downvote them. I am using Firebase Realtime Database. I want authenticated users to be able to add posts and change the score of other posts by upvoting or downvoting but I don't want them to be able to change the contents of or delete other posts. Is there a way to give users access to add new posts without letting them change existing posts? Also is there a way to make sure that when users change the score of a post they only increment or decrement it? I was thinking of doing something like this but it doesn't work:
{
"rules": {
".read": "auth.uid !== null",
"posts": {
".write": "auth.uid !== null",
"$postID": {
"$score": {
".validate": "newData.isNumber() && (newData.val() == $score + 1 || newData.val() == $score - 1)"
}
}
}
}
}
please limit yourself to one question per post
is there a way to make sure that when users change the score of a post they only increment or decrement it?
Yes that is possible, but in your code you're comparing newData.val() (the new value) to $score, which is the key (string) of the node. You'll want to compare against data.val() instead, so:
".validate": "newData.isNumber() && (
newData.val() == data.val() + 1 ||
newData.val() == data.val() - 1
)"
Note btw that .validate rules are not evaluated when a node is deleted, so you might want to do this in the .write rule if it should also prevent deletes.

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 push and set multiple nodes atomically with security rules [duplicate]

I want to create an increment field for article likes.
I am referring to this link: https://firebase.google.com/docs/database/android/save-data#save_data_as_transactions
In the example there is code for increment field:
if (p.stars.containsKey(getUid())) {
// Unstar the post and remove self from stars
p.starCount = p.starCount - 1;
p.stars.remove(getUid());
} else {
// Star the post and add self to stars
p.starCount = p.starCount + 1;
p.stars.put(getUid(), true);
}
But how can I be sure if the user already liked/unliked the article?
In the example, user (hacker) might as well clear whole stars Map like this and it will save anyway:
p.stars = new HashMap<>();
and it will ruin the logic for other users who were already liked it.
I do not even think you can make rules for this, especially for "decrease count" action.
Any help, suggestions?
The security rules can do a few things:
ensure that a user can only add/remove their own uid to the stars node
"stars": {
"$uid": {
".write": "$uid == auth.uid"
}
}
ensure that a user can only change the starCount when they are adding their own uid to the stars node or removing it from there
ensure that the user can only increase/decrease starCount by 1
Even with these, it might indeed still be tricky to have a security rule that ensures that the starCount is equal to the number of uids in the stars node. I encourage you to try it though, and share your result.
The way I've seen most developers deal with this though is:
do the start counting on the client (if the size of the stars node is not too large, this is reasonable).
have a trusted process running on a server that aggregates the stars into starCount. It could use child_added/child_removed events for incrementing/decrementing.
Update: with working example
I wrote up a working example of a voting system. The data structure is:
votes: {
uid1: true,
uid2: true,
},
voteCount: 2
When a user votes, the app sends a multi-location update:
{
"/votes/uid3": true,
"voteCount": 3
}
And then to remove their vote:
{
"/votes/uid3": null,
"voteCount": 2
}
This means the app needs to explicitly read the current value for voteCount, with:
function vote(auth) {
ref.child('voteCount').once('value', function(voteCount) {
var updates = {};
updates['votes/'+auth.uid] = true;
updates.voteCount = voteCount.val() + 1;
ref.update(updates);
});
}
It's essentially a multi-location transaction, but then built in app code and security rules instead of the Firebase SDK and server itself.
The security rules do a few things:
ensure that the voteCount can only go up or down by 1
ensure that a user can only add/remove their own vote
ensure that a count increase is accompanied by a vote
ensure that a count decrease is accompanied by a "unvote"
ensure that a vote is accompanied by a count increase
Note that the rules don't:
ensure that an "unvote" is accompanied by a count decrease (can be done with a .write rule)
retry failed votes/unvotes (to handle concurrent voting/unvoting)
The rules:
"votes": {
"$uid": {
".write": "auth.uid == $uid",
".validate": "(!data.exists() && newData.val() == true &&
newData.parent().parent().child('voteCount').val() == data.parent().parent().child('voteCount').val() + 1
)"
}
},
"voteCount": {
".validate": "(newData.val() == data.val() + 1 &&
newData.parent().child('votes').child(auth.uid).val() == true &&
!data.parent().child('votes').child(auth.uid).exists()
) ||
(newData.val() == data.val() - 1 &&
!newData.parent().child('votes').child(auth.uid).exists() &&
data.parent().child('votes').child(auth.uid).val() == true
)",
".write": "auth != null"
}
jsbin with some code to test this: http://jsbin.com/yaxexe/edit?js,console

Preventing child nodes from being removed in 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()"

Resources