'Securing Firebase RTDB with multiple databases for chat app

I am on the blaze plan and building a chat functionality in my app using Firebase realtime database.

I have 3 databases for now :

  • Main instance that contains sharding information and users chats' info.
  • The 2 other instances will contain the chats and messages.

The database structure for the main instance looks like this :

{
  "shards" : {
    "shard1" : {
      "full" : false,
      "name" : "shard1",
      "num" : 1,
      "url" : "shard1Url"
    },
    "shard2" : {
      "full" : false,
      "name" : "shard2",
      "num" : 0,
      "url" : "shard2Url"
    }
  }
}

And here is the data structure of one of the shard :

{
"users" : {
    "user_id_1" : {
      "chat_id_1" : true
    },
    "user_id_2" : {
      "chat_id_1" : true
    }
  },
  "chats" : {
    "chat_id_1" : {
      "created" : true,
      "createdAt" : 1645888918717,
      "members" : {
        "user_id_1" : true,
        "user_id_2" : true
      },
      "name" : "",
      "uid" : "chat_id_1",
      "updatedAt" : 1645895862214
    }
  },
  "messages" : {
    "chat_id_1" : {
      "mess_id_1" : {
        "content" : "Hello",
        "chatID" : "chat_id_1",
        "createdAt" : 1645895862214,
        "senderID" : "user_id_1",
        "uid" : "mess_id_1"
      },
      "mess_id_2" : {
        "content" : "Bye",
        "chatID" : "chat_id_1",
        "createdAt" : 1645889441313,
        "senderID" : "user_id_2",
        "uid" : "mess_id_2"
      }
    }
  }
}

I want the following rules to apply :
Reads :

  • A user can listen for changes under /users/$user_id/ where $user_id is auth.uid
  • A user can get all the chats from the above IDs when he is in the member map (under /chats/$chat_id)
  • A user can read all the messages from a chat when he is a member (under /messages/$chat_id)

Writes :

  • A user can create a chat if there is no data under the chat_id node
  • A user can update a chat if he is a member under the chat_id
  • A user can create a message if a chat_id exists and if he is a member under the chat_id and if the senderID matches the auth.uid
  • A user can delete a message if he is the sender of the message (not sure i can do this with security rules but only client side)
  • A user can add only an item like {"$chat_id": true} under any other user under /users/$user_id/

So far, here are my rules that are not working : -> Main shard :

{
  "rules": {
    "shards": {
      ".read": "auth.uid != null",
      ".indexOn": ["full"],
      "$shardName": {
        "num": {
          ".write": "auth.uid != null"
        },
        "$other": {
          ".write": false
        }
      }
    },
  }
}

-> For other shards :

{
  "rules": {
    "users": {
      "$userID": {
        ".write": "auth.uid != null && !data.exists()",
        ".read": "auth.uid == $userID",
      }
    },
    "chats": {
      "$chat_id": {
        ".read": "data.child('members/'+auth.uid).exists()",
        ".write": "(!data.exists() && newData.hasChild('members')) || data.child('members/'+auth.uid).exists()",  
      }
    },
    "messages": {
      "$chat_id": {
        ".read": "root.child('chats').child($chat_id).child('members').child(auth.uid).exists()",
        ".indexOn": ["createdAt"],
        "$mess_id": {
          ".write": "(!data.exists() && newData.child('senderID').val() == auth.uid) || (data.exists() && data.child('senderID').val() == auth.uid)"
        }
      }
    }
  }
}

EDIT :

I have made many edit regarding the database structure. I especially need help with reads rather than writes, as writes seem to be working fine now but i would like confirmation and / or changes if you see mistakes.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source