0

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.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
checkmate711
  • 3,301
  • 2
  • 35
  • 45

1 Answers1

0

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.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807