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

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.

Related

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 give premssion if has that child

So I am trying to give premission to read data from the parent but I am not sure what I am doing wrong, probably some syntax but I tryed a lot of combinations and I cant figure out why, what is the proper way to do it?
Thanks in advance!
tryed a lot of combinations with "}" but Its always says my syntax is wrong
"rules": {
"ca": {
"$date": {
"$game": {
///if($game.hasChild(auth.uid))
"$uid":{
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
}
if the user is from $game so is allowed to read data from there as well as read is own data.. Thanks!
Firebase security rules do not by themselves filter data.
If you want to retrieve only data that has a certain child, you'll need to query on the value of the child. E.g. ref.orderByChild("first_letter").startAt("A").endAt("Z"). Once you have such a query, you may be able to enforce it with query based security rules.
But your structure hints at a scenario that isn't well modeled. Your current data model makes it easy to find the users for a game, it does however not make it easy to find the games for a user. To make that equally easy, you'll need to add a secondary data structure where you store the game (IDs or data) for each user.
So something like:
"user_games": {
"$uid": {
"$gameid": true
}
}
Also see:
Firebase query if child of child contains a value
Firebase Query Double Nested

Firebase Rules: Read restriction for dynamic child nodes

I'm trying to implement a Firebase rules read restriction in a data model that has a few nested dynamic child nodes.
I have the following data model:
/groupMessages/<groupId>/<messageId>/
{
"senderId": "<senderId>",
"recipientId": "<recipientId>",
"body": "..."
}
groupId, messageId, senderId and recipientId are dynamic ids. I would like to attach a listener to the /groudId node to listen to new messages. At the same time I only want users to read the message where the senderId or recipientId matches a corresponding auth.token value.
Due to Firebase cascading rules, if I allow the read at the groupId level without restrictions, I can't deny them on the message level.
{
"rules": {
"groupMessages"
"$groupId": {
".read": "auth != null"
}
}
}
}
I also haven't found a way to restrict the read rule on the groupId level to check for sender/recipientId of a message.
Any suggestions greatly appreciated.
As you've found, security rules cannot be used to filter data. But they can be used to restrict what queries can be performed on the data.
For example, you can query for all messages where the current user is the sender with:
var query = ref.child("groupMessages").child(groupId).orderByChild("senderId").equalTo(uid);
And you can secure access to the group's messages to only allow this query with:
{
"rules": {
"groupMessages": {
"$groupId": {
".read": "auth.uid != null &&
query.orderByChild == 'senderId' &&
query.equalTo == auth.uid"
}
}
}
}
The query and rules now exactly match, so the security rules will allow the query, while they'd reject a broader read operation. For more on this, see query based rules in the Firebase documentation
You'll note that this only works for a single field. Firebase Database queries can only filter on a single field. While there are workarounds by combining multiple values into a single property, I don't think those apply to your scenario, since they only work for AND queries, where you seem to want an OR.
You also seem to want to query on /groupMessages instead of on messages for a specific group. That also isn't possible: Firebase Database orders/filters on a property that is at a fixed path under each child of the node where you run the query. You cannot query across two dynamic levels, as you seem to be trying. For more on this see: Firebase Query Double Nested and Firebase query if child of child contains a value.
The common solution for your problem is to create a list of IDs for each user, which contains just the IDs of all messages (and/or the groups) they have access to.
userGroups: {
uid1: {
groupId1: true,
groupId2: true
},
uid2: {
groupId2: true,
groupId3: true
}
}
With this additional data structure (which you can much more easily secure), each user can simply read the groups they have access to, and your code then reads/queries the messages in each group. If necessary you can add a similar structure for the messages themselves too.
Finally: this type of recursive loading is not nearly as inefficient as many developers initially think, since Firebase pipelines the requests over an existing connection.

Firebase rules with wildcards to read data if wildcard is known

I have sign up system where I want only users to sign up if they have a valid secret key which I shall provide to users who want to register. If key is in db, then proceed to sign up. Thus I have generated random non repeated 8 chars and stored them in the real time database in the following structure:
Secrets:
"x5f1n9v0":
"Status" : 1
"C8vT2xxY":
"Status" : 1
And so on
..
{
"rules": {
"secrets":{
"$secret": {
".read": true,
".write": false
}
}
}
}
First question regarding the aboves rules:-
In this case no one can add a new secret key ?
Also the read will only be valid if someone has a valid key from my list ? Nobody can read the whole list ? Any bugs in this ?
Now suppose another set of rules where I want to write to the child of each key iff the user knows the valid id.
If I change the rule for write to true, will this work and no bugs to hack it ?
"rules": {
"secrets":{
"$secret": {
".read": true,
".write": true
}
}
}
}
Thanks
In this case no one can add a new secret key?
With those first rules, only someone with administrative access can add keys.
Also the read will only be valid if someone has a valid key from my list? Nobody can read the whole list?
There is indeed no way to read the entire list with your first set of rules. Someone can only read a secret if they know its key.
With your second set of rules:
"secrets":{
"$secret": {
".read": true,
".write": true
}
}
Now anyone can write any secret. That is probably not what you want. If you want to only allow them to change the data that already exists under an existing key, you'll want to check if there is any data already:
"secrets":{
"$secret": {
".read": true,
".write": "data.exists()"
}
}
If you want them to not be able to change-but-not-delete the existing data, that would be data.exists() && newData.exists(). If you have additional requirements about the data formats users can write, you'll want to add those to a corresponding .validate rule.
Your last set of rules is both invalid and meaningless. It's invalid because $secrets is not defined. But even if it was defined $secrets === $secrets will always be true.

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