Search code examples
firebaseloopsgoogle-cloud-firestorefirebase-security

Run Loop in firestore rules


It is requirement to run loops in data validation. In my case I have document with schema:

interface SomeDoc {
    // ...other props
    "prop-with-map": { [key: string]: number };
}

still there is no way to validate SomeDoc["prop-with-map"]

I let user create this document, then they can't update so need to check schema in firestore-rules. Without loops or schema check support in rules I have to make background function.

OR

I know there can't be more then 5 fields in SomeDoc["prop-with-map"]. So I can check them one by one. Or create js function that generates code firestore-rule-function that checks in arr one by one.


Solution

  • SOLUTION

    /**
     * @param {string} functionName   what function name you would like.
     * @param {string} validate       what is validator function name.
     * @param {number} iteration      max iteration before exiting loop.
     * @return {string}               function code used in firestore rules
     */
    function generateFirestoreLoopForList(functionName, validate, iteration) {
      let func = `function ${functionName}(arr) {\n return arr.size() == 0 ? true : ${validate}(arr[0])`;
      for (let i = 1; i <= iteration; i++) {
        let addAtIndex = func.length - i + 1;
        func =
          func.substring(0, addAtIndex) +
          `&& (arr.size() == ${i} ? true : ${validate}(arr[${i}]))` +
          func.substring(addAtIndex);
      }
      func += "\n }";
      console.log(func);
      return func;
    }
    

    then your rules can go like

    rules_version = '2';
    service cloud.firestore {
        match /databases/{database}/documents {
            match /{document=**} {
                allow read, write: if false;
            }
            match /TEST/TEST {
                allow create: if checkOtherProps() && hasAllOdd(request.resource.data["prop-with-map"].values());
                function checkOtherProps() {
                    return // validate props and return accordingly;
                }
                function isInt(x) {
                    return x is int;
                }
                // outuput of generateFirestoreLoopForList("hasAllInts", "isInt", 5);
                function hasAllInts(arr) {
                    return arr.size() == 0 ? true : isInt(arr[0])&& (arr.size() == 1 ? true : isInt(arr[1])&& (arr.size() == 2 ? true : isInt(arr[2])&& (arr.size() == 3 ? true : isInt(arr[3])&& (arr.size() == 4 ? true : isInt(arr[4])&& (arr.size() == 5 ? true : isInt(arr[5]))))));
                }
            }
        }
    }