0
votes

How can one protect the structure if the input data is a json object composed of multiple json objects with un-predefinable unique keys per parent node (per user for example)?

The same question with an example:

  • Every user may have favorite foods.
  • Details of the food is not important as that information come from somewhere else.
  • A user may have more than -1 and less than 1001 number of favorite foods.
  • Only the changed data should be synced to minimise Firebase costs.

So, assuming a user has marked 3 foods with id of E5435, 59z195J, k311 as favorites: The following structure seems to be OK.

{ fav: { $uid { "E5435":1, "59z195J":1, "k311":1 } } }

But! How one would prevent the structure from expanding with arbitrary data? You could add validation rules to check data type and length etc.. but child names are undefined and can be anything like the above example.

I do not want to keep 1 long favorites string as that means sync won't work partially and I must overwrite the data and download every single time.

I cannot add all possible food IDs as it will make the structure very long and that list may grow any time.

How can one add a rule to secure this structure when possible input is something like this:

/fav/UID1234567/ <-- {"AAAA5":1,"G545345":1,"D454WW":1}

(Notes: Food IDs are intentionally written with mixed up characters. If that makes it easier to analyse the question, they can be replaced with names.

AAAA5 --> APPLE, D454WW --> RICE ) forming: /fav/UID1234567/ <-- {"APPLE":1,"RICE":1}

And keep in mind that there cannot be predefined food list to select from: So, nothing like [BANANA, APPLE, RICE, SUSHI,.....] exists. That HUGE list exists but outside of Firebase.

When the client downloads content of /fav/USERID/ fully (only 1 time), it will compare the whole list against some other list to filter those items and show them as favorites.

End of notes)

Additional info: To be clear, how would you prevent the usage of some long text instead of "APPLE" and again a long text instead of it's value '1'. As these data will increase firebase costs and can be considered as attacks. Usual validation rules do not seem to work as in this example we are not pushing values 1 by 1, but multiple of them. So, there is no newData().children().key.isString() and newData().children().key.length(), etc...

1
Do you confirm that, in a nutshell, you want to limit the length of the ids of the favorite food nodes. As well as forcing the value for such a node to be 1. Obviously, you are not going to be able to check that ids of the favorites food nodes are valid (i.e. are within the "HUGE" list), since you don't want to use this list in the Security rules. - Renaud Tarnec
Also, where is stored this "HUGE" list of "authorized" food ids? Could it be fetched through an API? - Renaud Tarnec
You are correct. I confirm what you have written in your first comment. That HUGE list is downloaded from a dedicated server and can change from time to time (new ids or deleted ids). That list is kept at the client in an sqlite database. That sqlite database currently has "favorites" table which just includes food ids. When that HUGE list is replaced, favorites stay intact and when requested that list is compared against the downloaded HUGE list. I am trying to migrate that "favorites" table to Firebase. - frankish

1 Answers

1
votes

So, if you want to limit the length of the ids of the favorite food nodes, as well as forcing the value for such a node to be 1, the following rules will do the trick:

{
  "rules": {
    "fav": {
      "$uid": {
        "$foodid": {
          ".read": true,
          ".write": true,
          ".validate": "newData.isNumber() && newData.val() == 1 && $foodid.length < 5"
        }
      }
    }
  }
}

Note that this rule will perfectly work for pushing only one node or several nodes.

You can try the following with the JS SDK:

var database = firebase.database();

var updates = {};
updates['fav/user456/AAA5'] = 1;
updates['fav/user456/ABVD'] = 1;
updates['fav/user123/YUIH'] = 1;

database.ref().update(updates);

Note also that you probably need to add the part for checking that the user is authenticated and write to his/her own parent node, i.e. ".write": "$uid === auth.uid".


Having said that, another approach could be to use a Cloud Function in order to write to the Realtime Database. In the Cloud Function you would:

  1. Fetch the list of food items (the "HUGE" list) through a REST API that you would expose from a server reading your sqlite DB;
  2. Verify that the node of the new fav food is within the list.

Of course, if the list is really HUGE, it might not be cost efficient... just an idea.

Read this article for more detail on this approach.