Firebase: Security rules for a collaborative app - firebase

I'm writing a note sharing app and I'm trying to find the best approach for the data structure to allow adding collaborators to user notes, while at the same time having sensible security rules for the structure in question.
What I have now is the following:
"users": {
"johndoe": {
"notes": {
"note1Key",
"note2Key"
}
},
"jane": {
"notes": {
"note3Key",
"note4Key",
"note5Key"
}
}
...
},
"notes": {
"note1Key": {
// actual note data
},
"note2Key": {
// actual note data
},
"note3Key": {
// actual note data
},
...
},
"shared": {
"johndoe" : {
"note5Key" : true,
"note3Key" : true
},
"jane" : {
"note1Key" : true
}
...
}
When "John Doe" creates a note, the note is stored in notes/noteKey with read/write access granted to owner and collaborators added by the owner. Additionally the note's key is stored in user/johndoe/notes/noteKey, which can be read and written to only by him. When this user wants to add a collaborator ("Jane") to his note, this same note key is stored in shared/jane/noteKey which can be globally read & written to. This way, when listing each user's notes, I have to read from only 2 locations to list all notes a user has access to: user/johndoe/notes and shared/johndoe.
Is there a better approach? I don't like to have the shared index globally accessible, could I somehow limit it? Since one user can potentially collaborate with a big number of different users on different notes, I'm not really sure how to set the security rules, to limit the read/write access to this index.
I was thinking about reversing the shared node logic, to store note key's under it's respectful owners sub-nodes and including a list of collaborators like so: shared/jane/noteKey/collaborators/johndoe. This way I could have a global read rule and a more restrictive write rule (each user can only write in his own shared node), however this would greatly increase the complexity of listing all notes a user has access to.

You wanted to:
allow adding owner & collaborators to user notes.
list all notes a user owned.
list all notes a user has access to.
You should have added collaborators list to each notes as follows:
{"rules":{
"users": {
"$user_id": {
"profile_and_settings":{
".write":"auth != null && auth.uid == $user_id"
},
"owned_notes":{
".write":"auth != null && auth.uid == $user_id",
"$note_id":{}
},
"accesssible_notes": {
".write":"auth != null",
"$note_id":{}
}
}
},
"notes": {
"$note_id": {
// to edit this node: must authenticated, new entry or owner of this node.
".write":"auth != null && ( !data.exists() || data.child('owner').val() == auth.uid )",
"owner":{
".validate":"newData.val() == auth.uid"
},
"collaborators":{
"$user_id":{}
},
// ... other note data
}
//...
}
}}
See related question:
Firebase rule: Do we have better ways to manage object ownership?

Related

firebase security rule which will prevent the updation or deletion of existing node

for example, if I have created a node (book) which have a child (author) with value ("name of author"). What I want is that in future no one can update or delete this child node. I know it is possible with firebase security rules but I am not able to figure out the optimal way to do so.
You could write a database rule to only allow creating new data and not editing anything once it's been written.
This allows you to create a new book if no data already exists at the $key location. It also ensures that newData exists and that author is a string variable.
{
"rules": {
"book": {
"$key": {
"author": {
".validate": "newData.isString()"
},
".write": "data.val() == null && newData.val() != null"
}
}
}
}

firebase realtime database security rules for non user data

So, I have an app where users can order the cakes and do other profile management, the rules looks like below:
{
"rules": {
"cakes" : {
".read": true,
".write": false
},
"users": {
"$user_id": {
".read": "auth != null && $user_id === auth.uid",
".write": "auth != null && $user_id === auth.uid"
}
}
}
}
Simply, they mean any one can read the cakes node (but no one can write). And an authenticated user can see or write to his on node.
This is good so far.
Now, my requirement is: When someone places an order through the app then i need to store it to firebase db in a top level node (lets say it orders). The question is what kind of security would be placed on orders node?
In functional definition: The app should be able to create new orders as user checks out, no one except seller should be able to read those orders, only seller should be able to have update access to a order.
If you want everybody to be able to write orders, and nobody able to read, the rules are simply the inverse of the ones for cakes:
"rules": {
"orders" : {
".read": false,
"$orderId": {
".write": true
}
},
With this anyone can push new data under /orders. And as long as you use push() to generate the new keys, you're guaranteed that they'll never conflict.
With these rules only a system-level admin can read the data. That is: you can read it in the Firebase Console, or someone can read it if they use the Admin SDK.
You might want to open it up for reading a bit more, e.g. by having the concept of an application-level administrator. Say that your UID is uidOfVik, you could model a list of admins in your database:
admins: {
uidOfVik: true
}
Now you can allow only admins to read the orders with:
"rules": {
"orders" : {
".read": "root.child('admins').child(auth.uid).exists()",
"$orderId": {
".write": true
}
},

What does $uid and auth.uid stand for?

Was going through some articles for understanding Rules & Permissions in Firebase and then came across conditions like this, for write operation :
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid"
}
}
}
}
I went with an understanding that $uid stands for Users Push ID and it applies to all dynamic ID's that are generated for Users node.
Then saw this rule else were :
{
"rules": {
"articles": {
"$article": {
"title": {
".write": "auth != null",
".validate": "newData.isString() && newData.val() != ''"
}
}
}
}
}
If $article stands for Push ID for articles node then Push ID for users node also should have been $user. Isn't it? What is the standard naming convention for declaring Push ID, when configuring rules, so that Firebase parses/understands them correctly.
Lastly, what does auth.uid stands for?
Lets start with auth.uid, this stands for the uid of the authenticated user.
Next up are $user and $article, these wildcard paths so they can be anything, not only push id's. Check out the docs for more info.
In your first example $uid is a wildcard for user id's. And with the write rule you check that the authenticated user can only write to his own location so it will be something like this (using names instead of uid's for clarity):
"users" : {
"Henk": {//Only Henk can write here
},
"John": {//Only John can write here
}
}
As for naming of wildcard paths there is no convention as far as i know. Personally i use descriptive names so i know what it is. Always $uid when using the users uid as a path and for the rest something like $objectID for object id's. (These can be push generated or something homebrew)
For the rest I suggest you take some time to read all the docs about security rules.

Firebase private chat schema and rules

I am trying to setup private chat abilities in an app that I am working on and I'm having a bit of trouble wrapping my head around denormalizing the data/setting up the rules properly.
After doing some reading, I realize that rules are all or nothing so using rules to filter is not an option.
I've sketched out my basic idea on paper, and have pasted it below. Basically there would be two main routes, users and chats.
Users would just be a keyed list, which each key matching an authenticated user. Then inside each member of the list I would just have each chat that the said user is in listed as a key.
For the chats route I'd have a list of all of the chats.
Now for the rules.
Users would only be able to read their data in the list where the key matched their uid. For the write i'm less confident. I'm thinking I have to let anyone with authentication write, otherwise the user starting the chat could not notify others of the new chat by playing the chat id in their chat list in the users route.
For the chats rules both read and write would only be allowed if the user is authenticated and the chat key is located inside their data in the user route.
Does the seem like I'm going in the right direction?
users:{
user1:{
chat1: true,
chat2: true
...
},
user2:{
chat1: true,
chat3: true
....
}
}
chats:{
chat1:{
lastUpdate: timestamp,
messages:{
0:{
from: user1
to: user2,
message: some message
}
...
}
}
}
rules:{
.read: false,
.write: false,
users:{
$user_id:{
.read: auth != null && $user_id == auth.uid,
.write: auth != null //not sure here as other users need to write here if the start a new chat
}
},
chats:{
$chat_id: {
.read: auth != null && root.child('users').child($chat_id).contains(auth.id),
.write: auth != null && root.child('users').child($chat_id).contains(auth.id)
}
}
}
I've been playing with this more, so here is one option (by no means am I suggesting this is the best way to do it)
Rules:
{
"rules":{
".read": false,
".write": false,
"users":{
"$user_id":{
".read": "auth != null && $user_id == auth.uid",
".write": "auth != null" //not sure here as other users need to write here if the start a new chat
}
},
"chats":{
"$chat_id": {
".read": "auth != null && root.child('users').child(auth.uid).child('chats').hasChild($chat_id)",
".write": "auth != null && (root.child('users').child(auth.uid).child('chats').hasChild($chat_id) || !data.exists())"
}
}
}
}
then for users I have a structure like this:
users:{
someUserId:{
chats:{ //embedded a second level so I can save firebaseObj.someUserId to get the keys more easily
someChatId: true //and repeat for each chat
}
}
}
Chats are like this:
chats:{
someChatId:{
//chat data
}
//more chat objects
}
I wouldn't be surprised if there is a much better way to do this, but at least this may be a start for those who are stuck. I'll try to remember to update this if/when I get a better solution.

excluding children from a firebase node with read access

I'm creating a simple chat application using firebase and am running into some issues with the available security settings.
The data model for this application is very simple and is as follows
rooms:[
people:[
{
name: //string
status: // what the user is doing, typing, still connected etc.
secret: // the problem is with this
}
],
messages:[
{/* message to and payload*/}
]
]
the issue is that I only want the user that created the rooms[i].people[j] to be able to update the status of that person.
Being new to firebase I though I would be able to use the update function as follows
personRef.update({
'status': // newStatus
'secret': // used to authorize the update
})
the problem with this is I can't find any way to make the secret write only and give access to the people at the same time. That is I need anyone to be able to pull the data located at rooms[i].people - meaning rooms[i].people would have to have ".read":true (in firebases security rules). But this would give read access to every child and anyone in the room would be able to see any one else's update secret. I'm I thinking of this problem incorrectly?
Is there a way to give read access to a parent but exclude some of the children from the results?
Thanks!
It depends a bit on how you're using the secret to implement authorization, but I suspect denormalizing your data is going to do the trick. Try something like this:
people-secrets:[
<user's ID>: {
secret:
}, ...
],
rooms:[
people:[
{
name: //string
status: // what the user is doing, typing, still connected etc.
}
],
messages:[
{/* message to and payload*/}
]
]
That would allow you to segment the security rules:
{
"rules": {
"people-secrets": {
"$user_id": {
".read": "$user_id === auth.uid",
".write": "$user_id === auth.uid"
}
},
"rooms": {
"$room_id": {
"$user_id": {
".read": "auth.uid != null",
".write": "$user_id === auth.uid && root.child('people-secrets/' + auth.uid + "/secret") === <that token>"
}
}
}

Resources