How to overcome the cascade in Firebase Realtime Database rules? - firebase-realtime-database

Given the following data structure:
items
categoryId1
itemId1
name
amount
dateAdded
itemId2
name
amount
dateAdded
categoryId2
itemId3
name
amount
dateAdded
I'd like to set the following rules:
If the user is logged-in using foo#gmail.com or bar#gmail.com, they should be able to add and update items.
Other logged-in users should be able to update only the item's amount.
I've tried the following Realtime Database rules:
{
"rules": {
"items": {
"$categoryId": {
"$itemId": {
".write": "auth !== null && auth.provider === 'password' && (auth.token.email === 'foo#gmail.com' || auth.token.email === 'bar#gmail.com')"
"amount": {
".write": "auth !== null && auth.provider === 'password'"
}
}
}
}
}
}
But, this doesn't allow someone_else#gmail.com to update the item's amount. The following promise gets rejected with "Permission Denied":
app
.database()
.ref(`items/${categoryId}`)
.push({
name,
amount,
dateAdded,
})
I know that with Realtime Database, rules work from top-down, with shallower rules overriding deeper rules.
So, what's the idiomatic way to overcome this? Is it possible to achieve the requirements above using this data structure, or do I need to restructure my data?

The problem was that the .write needed to be under $categoryId, not under $itemId. Thanks Frank!

Related

How can I ban a User in Firebase realtime Database using the rules?

I've created a list of banned users in my realtime Database and I want to make them impossible to log-in, I know I can use the database rules, but I don't know how, can someone help me?
This is my database structure:
/banned-users:
/UserId1:True
/UserId2:True
Those are my database rules:
{
"rules": {
".read": true,
".write": true
}
}
You need to reference data in other paths as follows:
{
"rules": {
"thepath": {
".read": "root.child('banned-users').child(auth.uid).val() !== true"
".write": "root.child('banned-users').child(auth.uid).val() !== true"
}
}
}
Note that actually you don't "make them impossible to log-in", because this is not possible, but you prevent them writing to/reading from your database.

Firebase Write Rule for Item with nested approved users

I have a realtime database with users and teams. Each team has a list of approved users under settings.. I want to create a rule so only approved users can write to their team...
something like:
I would not recommend Custom Claims because they are very limited in space.
You can write the rule very easy using the exists clause:
"teams": {
"$team_id" : {
".read": "true",
".write": "auth != null && root.child('teams/'+$team_id+'/settings/approvedUsers/'+auth.uid).exists()"
}
}
This rule will also deny a creation if there is no user on that path. You can then create the team from the backend or mark some users as admins or if anyone can create a team you can add a clause that checks if there is any data before and if not the rule befor should not get used. Something like this:
"teams": {
"$team_id" : {
".read": "true",
".write": "!data.exists() || (auth != null && root.child('teams/'+$team_id+'/settings/approvedUsers/'+auth.uid).exists())"
}
}
Here we check if the team already exists and if not anyone can create it. If it exist only users under approvedUsers can change it. That way the first user creating the team can create it. But don't forget that he needs to add himself to approvedUsers.
If you are working with emails for users that are not already in the system you could use the email instead of an uid as key and just set the value of the key to true. Just make sure te remove all chars that are not allowed as key from the email withe something like:
email.replace('.','')
And also do the same in the rules like:
"teams": {
"$team_id" : {
".read": "true",
".write": "auth != null && root.child('teams/'+$team_id+'/settings/approvedUsers/'+auth.token.email.replace('.','')).exists()"
}
}
The databse would then look like:
teams
teamID1
approvedUsers
email1#test.com: true
email2#test.com: true
teamID2
approvedUsers
email1#test.com: true
email3#test.com: true

Firebase rule to avoid parent overwrite

In my db, I have a node like this on root:
-dbroot
--usernameuid
---user1:'someid'
---user2:'someid'
---user3:'someid'
...
for creating this I am using code below:
database().ref('usernameuid/' + that.state.username).set(auth().currentUser.uid).then(() => {
...
Recently, I am not sure how it happened but somebody managed to delete or overwrite all --usernameuid node.
As my investigation, I found out blank username passed despite i am checking username with regex.
Anyway, firebase got blank username (that.state.username) and overwrote like --usernameuid: 'someid' and all data gone.
For prevent this not to happen again in future, (app is live so cant modify code) what kind of firebase rule can i write for:
user can only add child to --usernameuid
can't overwrite all like --usernameuid: 'something'
can't delete anything under --usernameuid
I found solution like this:
{
"rules": {
".read": "auth.uid !== null",
...
"otherchild1": {
".write": "auth.uid !== null",
},
"otherchild2": {
".write": "auth.uid !== null",
},
...
"usernameuid": {
"$username" : {
".write": "auth.uid !== null",
}
},
Summary: No rule at parent, specify .write in child you want. But on root, you need to delete global .write rule and specify seperately for each child hence firebase does not support cascade rules.

Firebase Realtime Database Rules - Using a variable as a key in the data key value pair when writing

This is a rough example of what my database looks like.
"userA": {
"uf": {
"userB": "0"
}
},
"users": {
"userA": "0",
"userB": "0",
"userC": "0"
}
And this is a rough example of the rule I am trying to write.
//USER ID
"$uid": {
//USER FRIENDS
"uf": {
//FRIEND USER ID
"$fuid": {
".write": "$uid === auth.uid &&
root.child('users').hasChild($fuid)",
}
},
},
And this is what I am trying to get working in the simulator
//Location /userA/uf/
//Data { "userC": "0" }
It seems that the security rule will always deny a write when the "key" for a data key value pair is a variable in my rules, in this case "$fuid". The simulator will return the messages "Simulated set denied" and "Write denied" but won't give me any additional details. I could get around this by writing the following.
//Simulation Method set
//Location /userA/uf/userC/
//Data { "0": "0" }
But this feels like it's writing unnecessary data to my database. What is the best practice here? Thanks.
Your rules give access to {uid}/uf/{fuid} but you're trying to write at {uid}/uf.
That {"0": "0"} is indeed unnecessary, you can just write "0".
If you want to write multiple friends at once, you can perform a multipath update, or modify your rules to allow writing directly at {uid}/uf and ".validate" the children.
Side note: if your users can be deleted, if user A has user B as a friend and user B is deleted, your rules won't allow user A to remove user B from the friends list. You should take care of that by changing the rules to allow the deletion of friends that do not exist, or by setting up an onDelete() triggered cloud function that would do the cleanup.

Firebase security and rules

Just a quickie, I'm trying to get my head around Firebase security protocols and I have set up a database called UsersDB which will store details based on auth.uid. The details being full name, email, provider, account created date, last login date.
I have setup a rule as follows:
{
"rules": {
".read": "auth != null", // only authed users can read/write
".write": "auth != null",
"UsersDB": {
"$uid": {
".read": "auth.uid == $uid", // users can read/write their own data
".write": "auth.uid == $uid"
}
}
}
My understanding is that the record will only be able to be read and written by the person whose user_id matches the auth.uid.
My question is have I done this correctly and if not how should I have achieve this? I only want the person creating the account to be able to read and write to this and no other uid to access the information.
Lastly, as a administrator of the firebase account. I would be thinking of going down the line of creating a admin console type software which would allow me access to all the data stored. How would I change or update the rules to allow an admin login to access the data above. Would I change the read access to anyone (although this would seem to me to leave a vulnerability in the rules) or is there a way to declare a rule giving my (admin) full read access to all data?
Thanks
You're overlooking a very important part of the Firebase documentation that specifies that permissions cascade:
SECURITY AND FIREBASE RULES WORK FROM THE TOP-DOWN
The child rules can only grant additional privileges to what parent nodes have already declared. They cannot revoke a read or write privilege.
Since your top-level read and write rules already allow any authenticated users to read/write all accounts, you cannot revoke that privilege lower in the tree.
Luckily there is no need in your scenario to grant these higher-level permissions.
{
"rules": {
"UsersDB": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid"
}
}
}
}
With this each user can only read and write their own data.
Keep in mind that Firebase rules are not filters. With the structure above, no user can query on /UsersDB, since nobody has read permission there.

Resources