1

I'm working on a simple app that maintains a transaction ledger.

My data looks like this:

{
    "payments": {
        "user1": {
            "payment1": {
                "amount": 2,
                "timestamp": 1475303826
            }
        }
    },
    "purchases": {
        "user1": {
            "purchase1": {
                "amount": 0.25
                "timestamp": 1475303735
            },
            "purchase2": {
                "amount": 1.99
                "timestamp": 1475303792
            }
        }
    },
    "users": {
        "user1": {
            "balance": -0.24
        }
    }
}

Everytime I add a payment, I want the user's balance to increase, and when I add a purchase, I want the balance to decrease.

Is there a way to setup Firebase rules so that a user's balance is always equal to the sum of their purchases and payments?


Edit

Following Frank van Puffelen's answer, I managed to create these rules:

"purchases": {
  "$uid": {
    "$pid": {
      ".validate": "!data.exists() && (newData.parent().parent().parent().child('users/'+$uid+'/balance').val() == root.child('users/'+$uid+'/balance').val() - newData.child('amount').val())"
    }
  }
},
"payments": {
  "$uid": {
    "$pid": {
      ".validate": "!data.exists() && (newData.parent().parent().parent().child('users/'+$uid+'/balance').val() == root.child('users/'+$uid+'/balance').val() + newData.child('amount').val())"
    }
  }
}

These make sure that any new purchase or payment is accompanied by a matching update to the user's balance.

I'm now trying to come up with rules that would prevent changing the balance without adding a purchase or payment. I had the idea to add a rule to a user's balance to verify that, during an update, there was exactly one new purchase or payment, but I don't think there is a way to count a node's children using the rules DSL.

David Thibault
  • 8,638
  • 3
  • 37
  • 51

1 Answers1

1

There is no way to express that a value must be the sum of all other values in security rules. That's inherently a non-scalable operation, since it requires evaluating all data on each update.

Closest I can think of is a system where you evaluate the current balance, the transaction amount and the new balance for every update. So (extremely simplified) something like:

newData.child("balance").val() == data.child("balance").val() - data.child("amount").val()

This solution is close to what I did a while ago for ensuring that a voteCount and a list of voters are kept in sync. See Is the way the Firebase database quickstart handles counts secure?

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