How can I refer to a child wildcard variable in Firebase - 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
}
}
}
}
},

Related

Firebase realtime database leaks protected data in websocket connection?

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
}
}
}
}

Firebase rules: Unknown variable '$uid'

I want user to only access their own content, except for one child node: common
In common child node I want all signed in users to have access.
I have made the following rules:
{
"rules": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
},
"common" : {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
Firebase gives me the error:
Error saving rules - Line 8: Unknown variable '$uid'.
The error appears in this line: ".read": "auth != null && auth.uid == $uid",
Based on your question, this is your desired database structure:
{
"userIdA": { // anything here can be written by only userIdA
"name": "Tom", // this is just example data
"location": "London",
/* ... */
},
"userIdB": { // anything here can be written by only userIdB
"name": "Sarah",
"location": "New York",
/* ... */
},
/* ... other user data ... */
"common": { // anything here can be written by signed in users
"data1": "some value",
"data2": "some other value",
}
}
The rules for this structure would be:
{
"rules": {
"common" : {
".read": "auth != null", // logged in users can read
".write": "auth != null" // logged in users can write
},
"$uid": { // $uid will be the value of any key, that isn't listed above it (in this case, anything other than "common")
".read": "$uid === auth.uid", // only the matching user can read
".write": "$uid === auth.uid" // only the matching user can write
}
}
}
Note: This data structure isn't very secure. Allow read/write access to only what you need in your database. With this structure, any user could come along and open up their console and delete everything under "/common". You may consider adding ".validate" rules to make sure certain keys (such as "/common/data1") are only strings.
The $uid must be inside users Document like the example below :
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}

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 rule to only allow deletion of old data by any authenticated user

The question is about the ships branch expanded below. Everywhere only authenticated users are allowed to write their OWN data everywhere. However, there is one exception. ANY authenticated user may read data out of the ships branch.
So far I had no trouble but here is one special rule:
ANY authenticated user may delete a child under any uid in the ships branch provided that the timestamp is 10 seconds or older.
I want any user to be able to call:
firebase.database().ref('/ships/gp3tJa3tgThukt39EejqJpZq12L2/granit').remove();
uid: gp3tJa3tgThukt39EejqJpZq12L2
shipid: granit
And be granted the rights to delete only.
In order to check the age of the record I store a firebase.database.ServerValue.TIMESTAMP in the perf array at index 0 (I am using an array here because this record is frequently updated and I want to keep data to a minimum).
On the client side, the program can see when a record is likely to have expired and only then call remove. This in order to avoid wasteful failed calls.
I need some help to define the correct rule. The line in question is highlighted in the second example where I attempted to define this rule.
{
"ships": {
"EnBawzb0CjZVgAKrMZD4HE3k5rW2": {
"oasisoftheseas": {
"param": {
"scale": 0.33075936163570846,
"type": "cruise/royalcaribbean/oasisoftheseas"
},
"perf": {
"0": 1,
"1": 1.11014724E7,
"2": 1.70473256E7,
"3": 115.7,
"timeStamp": 1475144447302
}
}
},
"gp3tJa3tgThukt39EejqJpZq12L2": {
"granit": {
"param": {
"scale": 0.12235531736978282,
"type": "riverbarge/granit"
},
"perf": {
"0": 5,
"1": 2.05622392E7,
"2": 13154087,
"3": 285.9,
"timeStamp": 1475144450086
}
}
}
}
}
Below the rules. It is the write rule for $shipid that I am interested in defining correctly.
{
"rules": {
"anchors": {
"$uid":{
".read": "auth.uid === $uid",
".write": "auth.uid === $uid"
}
},
"completed": {
"$uid":{
".read": "auth.uid === $uid",
".write": "auth.uid == $uid"
}
},
"ships": {
".read": "auth !== null",
"$uid":{
".write": "auth.uid === $uid",
"$shipid":{
".write": "((auth !== null) &&
(now - data.child('perf').child('timeStamp').val() >= 10000))"
}
}
},
"shipslog": {
"$uid":{
".read": "auth.uid === $uid",
".write": "auth.uid === $uid"
}
}
}
}
Well, I figured it out as confirmed by Frank. The solution edited in the question is correct, secure and working.
"ships": {
".read": "auth !== null",
"$uid":{
".write": "auth.uid === $uid",
"$shipid":{
".write": "((auth !== null) &&
(now - data.child('perf').child('timeStamp').val() >= 10000))"
}
}
},...
Any data can be written to the ships branch by the owner of the record. At a deeper level I use the uid wildcard and shipid wildcard to get to the data that can expire and needs to be removed.
The timestamp is secure as it is provided by the server on the server side. Deleting a ship can only happen by any authenticated user AND provided the difference between now and the timestamp is greater then 10000 milliseconds.
This is probably a nice clean generic way to let client apps cleanup old data without the need for serverside business logic.

Resources