Firebase realtime database leaks protected data in websocket connection? - firebase

I have defined a realtime database rule as follows:
{
"rules": {
".read": false,
".write": false,
"devices": {
".read": "auth.uid != null && query.orderByChild == 'ownerUid' && query.equalTo == auth.uid",
"$device": {
".read": "data.child('ownerUid').val() == auth.uid",
"nickname": {
".write": "data.parent().child('ownerUid').val() == auth.uid",
".validate": "newData.isString() && newData.val().length < 30"
},
"ownerUid": {
".validate": "root.hasChild('users/' + newData.val())"
},
... additional fields here
}
}
}
}
In my web application, using reactfire and firebase npm modules, I have queried for a device as follows:
const devicesRef = useDatabase()
.ref(`devices`)
.orderByChild('ownerUid')
.equalTo(user.uid);
const { data: devices, status } = useDatabaseListData<Device>(devicesRef, { idField: 'id' });
This appears to work, but if I look in the network tab, I can see all of the data come back, not just the data that is supposed to come back. The data returned to my code is the data that I would expect.
Note in the screenshot below that all data comes back, even data that does not have ownerUid defined.
I am using the example from the documentation almost exactly: https://firebase.google.com/docs/database/security/rules-conditions#query-based_rules
Am I doing something wrong? or is this a bug in Firebase?

I discovered the solution after upgrading my firebase client version and getting some new errors from it. It turns out the issue was that I was missing an index on ownerUid.
The new rules look like this:
{
"rules": {
".read": false,
".write": false,
"devices": {
".indexOn": ["ownerUid"],
".read": "auth.uid != null && query.orderByChild == 'ownerUid' && query.equalTo == auth.uid",
"$device": {
".read": "data.child('ownerUid').val() == auth.uid",
"nickname": {
".write": "data.parent().child('ownerUid').val() == auth.uid",
".validate": "newData.isString() && newData.val().length < 30"
},
"ownerUid": {
".validate": "root.hasChild('users/' + newData.val())"
},
... additional fields here
}
}
}
}

Related

Firebase database rules "hasChildren"

I was writing security rules in my database but I do not understand why the validation does not pass it ..
I just want people to be able to type in "Extra" if the key says "nombre".
In case it's not the key "nombre", don't let it.
the rules:
"rules": {
"Users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid && root.child('Users').child(auth.uid).exists() === false || newData.hasChild('Extra')",
".validate": "newData.hasChildren(['nombre'])",
"Extra":{
}
}
}
}
Is the writing rule wrong? Does someone explain to me why?
TEST1 wrong
TEST2
You're writing to location /Users/$uid/Extra, so the nombre property ends up in /Users/$uid/Extra/nombre. To test the rule, you'll want to write to /Users/$uid.
If instead you want to allow the JSON like this:
Users: {
myUserId: {
Extra: {
nombre: "MT Designer"
}
}
}
Then your rules currently don't work, because you're validating that nombre exissts under myUserId. It should be:
{
"rules": {
"Users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid && root.child('Users').child(auth.uid).exists() === false || newData.hasChild('Extra')",
"Extra":{
".validate": "newData.hasChildren(['nombre'])",
}
}
}
}
}
Update: 2020-07-10
From the new screenshots it seems that yyou haven't applied the change from above yet, so I'd first recommend doing that. But if you want to reject other child nodes in Extra, you can do that by changing the rules to:
"Extra":{
"nombre": {
".validate": "data.isString()"
},
"$other": {
".validate": false
},
}
So this validates that the name is a string, and rejects all other child nodes.
From comments, apparently this is what OP ended up with:
"Users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid && root.child('Users').child(auth.uid).exists() === false || newData.hasChild('Extra')",
"$othernode": {
".validate": false
},
"Extra": {
"nombre": {
".validate": "newData.isString()"
},
"$other": {
".validate": false
},
}
}
},

Firebase Database Rules to match row

I am using a Firebase Realtime Database. I have the following data:
I also have the rules:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"chat": {
"$key": {
".read": "data.child('memberId1').val() === auth.uid && data.child('memberId2').val() === auth.uid",
".write": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid"
}
},
The initial rule works perfectly:
".read": "auth != null",
".write": "auth != null",
Problem
The following 2 rules have no effect.
"chat": {
"$key": {
".read": "data.child('memberId1').val() === auth.uid && data.child('memberId2').val() === auth.uid",
".write": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid"
}
},
As you can see, in order to test these rules, in the first rule, I have made an impossible condition of memberId1 and memberId2 both equal to the users uid. As a result I would expect it to fail.
If I remove:
".read": "auth != null",
".write": "auth != null",
and just have:
"chat": {
"$key": {
".read": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid",
".write": "data.child('memberId1').val() === auth.uid || data.child('memberId2').val() === auth.uid"
}
},
Then access is denied. Even if I change it to:
"data.child('memberId1').val() === 'h6qQg5YfQveTaCyBEXwDMSJPqwk1'
The following is also denied:
"chat": {
"Ko7w9XTtuRVN4p6CMp7": {
".read": true,
Question
How should I structure the rules to allow that a user may only access a row where their uid matches either memberId1 or memberId2?
Thanks
UPDATE
I have the following code:
findChats(): Observable<any[]> {
return this.af.database.list('/chat/', {
query: {
orderByChild: 'negativtimestamp'
}
}).map(items => {
const filtered = items.filter(
item => (item.memberId1 === this.me.uid || item.memberId2 === this.me.uid)
);
return filtered;
});
}
My question is similar to this one. I try the following with no success:
{
"rules": {
"chat": {
"$id": {
".read": true
}
},
Firebase rules are atomic. So if you try to read /chat (and thats what you are currently doing) it will only check the /chat branch rules. Since you dont have any rule in /chat it goes for the default thats is not giving access. Therefore, your rules would only be evaluated in case you were trying to read /chat/chatId.
One possible solution you could go for is to store a list of chats which each user is part of. So you can keep your current chat branch but store another branch in the database with the following structure:
user_chats: {
uid1: {
chatId1: true,
chatId2: false
}
uid2: ...
}
And rules:
"user_chats": {
"$uid": {
".read": "auth.uid === $uid",
".write": "auth.uid === $uid"
}
}
Then you could keep your chat rules like you already have them but first get the data from /user_chats/uid and then for each chatId retrieved you you will need to read on chat/chatId.

Firebase rules length validation error

I've been a while working with firebase and I love it but today I'm working on security rules and I'm getting an error with simulator, my code looks as below:
{
"rules": {
"users":{
"$uid":{
".read": "auth.uid != null",
".write": "auth.uid != null",
".validate":"newData.child('profile').child('userName').isString()&& newData.val().length < 15"
}
}
}
}
The error appear just when i add the lenght validation. When I do:
{
"rules": {
"users":{
"$uid":{
".read": "auth.uid != null",
".write": "auth.uid != null",
".validate":"newData.child('profile').child('userName').isString()"
}
}
}
}
Works fine, any idea why this is happening, I have readed the documentation on: https://firebase.google.com/docs/database/security/securing-data and many other examples and I just can't find the error. Thank you su much in advice and happy coding.
You can add validation like this to your field as per this example.
{
"rules": {
"users": {
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid",
".read" : "$user_id === auth.uid",
"familyName" : ".validate": "newData.isString() && newData.val().length > 1 && newData.val().length < 100",
"givenName" : ".validate": "newData.isString() && newData.val().length > 1 && newData.val().length < 100",
"age" : ".validate": "newData.isNumber() && newData.val() > 13 && newData.val() < 110",
"email": {
// an email is only allowed in the profile if it matches
// the auth token's email account (for Google or password auth)
".validate": "newData.val() === auth.email"
}
}
}
}
}
Ok I have solved the correct syntax:
{
"rules": {
"users":{
"$uid":{
".read": "auth.uid != null",
".write": "auth.uid != null",
".validate":"newData.child('profile').child('userName').isString()&& newData.val().length < 15"
}
}
}
}

How can I refer to a child wildcard variable in Firebase

In an iOS messaging app I am creating, an outline of the structure for the messages node is as follows
"messages": {
"$uid":{
".read": "auth.uid == $uid",
"$messageId":{
// insert .write rule here
"toUid":{
// if its a group message then there would be multiple "$toUid" children
"$toUid":{
},
},
"fromUid":{
"$fromUid":{
}
},
"timeStamp":{
},
"group":{
"isGroupMessage":{
},
"groupId":{
}
}
}
}
},
I would like to add a write rule after $messageId to ensure that the "$toUid" or "$fromUid" is equal to the $uid .
any ideas how I can do this?
Is the following the best way to do it:
".write": "newData.child('toUid').hasChild($uid) || newData.child('fromUid').hasChild($uid)",
** I have gone with the solution below - changes structure but is the quickest / safest security rules for us to use for now.
The MVP is initially not going to have group messages (may be irrelevant but the logic could be different) - so alternatively I could add a wildCard child variable "$friendUid" and then have the following rule below that.
"$messageId":{
"$friendUid":{
".write": "$uid == auth.uid || $friendUid == auth.uid",
So this is how I decided to structure my Firebase database rules for my messages node. I added an extra child node with a wildcard variable of "$friendUid", in order to easily refer to it and lockdown my messages branch.
"messages": {
"$uid":{
".read": "auth.uid == $uid",
"$messageId":{
"$friendUid":{
".write": "$uid == auth.uid || $friendUid == auth.uid",
".validate": "root.child('friends/'+$uid+'/'+$friendUid).exists()",
"toUid":{
"$toUid":{
".validate": "$toUid == $friendUid || $toUid == $uid"
},
},
"fromUid":{
"$fromUid":{
".validate": "$fromUid == $friendUid || $fromUid == $uid"
},
},
"timeStamp":{
".validate":"newData.isNumber()"
},
"$other":{
".validate": false
}
}
}
}
},

Unable to validate child nodes with Firebase security

I'm clearly missing some fundamental aspect of firebase security, because this shouldn't work. I would expect it to throw a validation error when attempting to push invalid data. (Inserting a new node into /nodes)
Rules:
{
"rules": {
"nodes": {
".read": "auth !== null && auth.provider === 'google'",
".write": "auth !== null && auth.provider === 'google'",
"user": {
".validate": "newData.val() === auth.uid"
},
"ts": {
".validate": "newData.val() <= now && newData.val() >= (now-1000*60*60*24)"
}
}
}
}
Then in my console I try to intentionally insert invalid data:
ref.child('nodes').push({
'user': 'abc',
'ts': 123
}, function(err){console.log(err);});
Which logs null, and when I check my database it was inserted, no validation errors! I know I've got something fundamentally wrong, because a validation rule right after the .read and .write rows of the following disallows any writing. .validate": "newData.hasChildren(['user', 'ts'])",
{
"nodes" : {
"-KAgH0BLneWfGu8NymBo" : {
"ts" : 123,
"user" : "abc"
}
}
}
Whoops. Missing "$node_id"
{
"rules": {
"nodes": {
"$node_id":{
".read": "auth !== null && auth.provider === 'google'",
".write": "auth !== null && auth.provider === 'google'",
"user": {
".validate": "newData.val() === auth.uid"
},
"ts": {
".validate": "newData.val() <= now && newData.val() >= (now-1000*60*60*24)"
}
}
}
}
}

Resources