3
votes

I have put a rule as

(root.child('users').child(auth.uid).child('role/cafe').val() === true)

in my realtime database rules.

My user is

key {
 uid: xxxx 
 name: xxxx
 role:{
  cafe: true
  user: false
  }
}

if auth.uid is equal to key, the rule works, however how can I modify the above rule to look for uid inside data

2
What is the expected value of $key? How do you get the value of $key when you want to look up a user's data (such as in another rule)? For these reasons, I highly recommend against making it anything other than the user ID and using lookup indexes (e.g. username: uid).samthecodingman

2 Answers

3
votes

It is unclear what the value of $key is expected to be. So I'm going to assume it's just some random string that isn't used in the rules.

For each security rule, the current data of the node is accessible using the predefined variable data. ".write" and ".validate" rules also have access to the to-be-written data as newData. These are both RuleDataSnapshot objects.

Assuming that a user making changes must be the user given by the uid property, the following rules can be used.

"rules": {
  "users": {
    "$key": {
      ".read": "auth != null && data.child('uid').val() == auth.uid",
      ".write": "auth != null && ((data.exists() && data.child('uid').val() == auth.uid) || (!data.exists() && newData.child('uid').val() == auth.uid)"
    }
  }
}

The above rules use a fail-fast approach. If a user is not logged in, the check aborts. Otherwise, the user's ID is matched against the existing data at the given node. If the data doesn't yet exist, the newly updated data must also match the current user's ID.

In case the "cafe" role is important, the following rules also require that the "cafe" is set to true to allow read/write operations.

"rules": {
  "users": {
    "$key": {
      ".read": "auth != null && data.child('uid').val() == auth.uid && data.child('role/cafe').val() == true",
      ".write": "auth != null && ((data.exists() && data.child('uid').val() == auth.uid && data.child('role/cafe').val() == true) || (!data.exists() && newData.child('uid').val() == auth.uid && newData.child('role/cafe') == true))"
    }
  }
}

Note: If $key is storing user info/data, I highly recommend using the user's ID as the key as security rules cannot perform queries like "does userid have admin role?" without structuring your data to allow it. If $key is meant to be a username, instead use a username-to-userID map as it will prevent future problems. One such example is if a user wants to change their username, you can remove and link the new username to the user without ever having to move all their data.

2
votes

You can use a wild card. In your rules

"users":{
  "$key":{
    ".read" : (root.child('users'+$key).child('role/cafe').val() === true) && (root.child('users'+$key).child('uid').val() === auth.uid  ),
    ".write" : (root.child('users'+$key).child('role/cafe').val() === true  ) &&(root.child('users'+$key).child('uid').val() === auth.uid  )
  }
},

The first condition checks whether the café value for the user is true the second checks whether the uid is the same