Security Rules - lists of data that can be read? - firebase

So my application has 'conversations' that are shared between many users. 'conversations' have many 'users'.
I am able to get authors authorized for ".read" using the simulator on a specific conversation, but I'm not sure how to get a list of conversations when logged in as a user -- I can't use .on("child_added") on 'conversations', because .read is not allowed on 'conversations', only on some of their childs depending on the logged in user.
How should I proceed?
Thanks!

We don't recommend using security rules as a way to implement filtering of records. You can store a list of conversations under a global list, but then store the IDs of those conversations under each user that has access to them. This means that you'll also have to ensure that the list of conversations for each user is updated whenever there is a change.
/conversations
<conversation-id-1>
<conversation-id-2>
/users
<user-1>
/conversations
<conversation-id-1>: true
<user-2>
/conversations
<conversation-id-2>: true
Doing a .on("child_added") on /users/user1/conversations will give you access to all the conversations that the user has access to. Make sure to set the permissions on the top-level conversations list appropriately.
See https://www.firebase.com/blog/2013-04-12-denormalizing-is-normal.html for some more background on how to structure your data in Firebase.

Related

Firestore Security Rules Shared Document by Group

I have a situation where a user can create a doc and then share it with a group of other users. They could share it to multiple different groups. I don't know how to set a rule for this.
Here is the database structure:
So in the group you have a list of docs that have been shared to it. My app loads the group that a user is in, then wants to load all the docs in the documents array. I need a way server side to say that this is OK. Up until now only the owner of the doc can read it.
I put a field in each doc that contains ids for each group its shared to. I think I want to say "check if the user is a member of any groups in the sharedToGroups" list but I can't work out how to do that unless I maintain another list somewhere say in the userProfile doc that has a list of circles the user is a member of. Even then I'd be trying to compare 2 lists and I'm not sure I can do that client side.
It would be nice to be able to get the group Id somehow from where the request is being issued from and just see if that is in the sharedToGroups array.
Any help or comments on how this can be achieved would be greatly appreciated, maybe it needs a different db structure.
You can try an approach of this sort:
I am not sure if this will help you but off the top of my head maybe you could enable permissions on firestore for the group document. As in, in the rules, for the group set up a function that validates the user with the user ID stored in the document with the ID attached in the auth via the firebase auth
Therefore, rather than trying to restrict access per document, restrict access per group.
I'm going to answer my own question. Not sure if its the correct protocol here (not a professional programmer or experienced Stack Overflower) but it might help someone.
I ended up adding a field in the user_profiles document that has a list of each group they are in. This list needs to be maintained as I create and add / remove people from groups along with the members list in the group itself.
The benefit of this is that I can use the users id from the request object to get that document from the data base in the security rule. I then have a 'sharedToGroup' array in the doc I'm trying to access and a "inGroups" array in the user_profile that I can access also. Then I use the hasAny operator to compare the two arrays and allow access if the sharedToGroup array has any values from the inGroups array.
My rule becomes:
match /_group/{groupId}{
allow create: if isSignedIn();
allow read: if isOwner()
|| resource.data.sharedToGroup.hasAny(get(/databases/$(database)/documents/user_profiles/$(request.auth.uid)).data['inGroups']);
allow write: if isOwner();
}
Only thing left to do is to secure the user_profiles doc to make sure not even the user can write to it since I don't want someone manually adding groups into their array.
I hope this might help someone someday - like I said I'm a not a pro here so take it with a grain of salt.

Can firebase security rules be used to deliver the right data or its only here for malicious users?

Imagine the case I want to request a chat (a document) from two user Ids (user1 and user2).
Chat document contains an array with participants ids so users1 and users2 are both in particiants array.
If I request everything that contains user1 or user2 in participant array I may get user1 chats with other users too, not only user1 with user2.
Code for the example :
ref.where('participantsIds', 'array-contains-any', [u1.id , u2.id])
Now imagine I add a security rule that only allow getting chats if the user making the request is in participants array.
Rule for example :
allow read, write: if request.auth!=null && request.auth.uid in resource.data.participantsId
Is this a bad practice? Can I use rules to control data behaviors for each instance of the app?
The rule will probably add some milliseconds (or not even?) to the request no matter if the result is true or false.
And yes I know will affect cost and request quotas.
Thanks !!
Firebase security rules are not filters. They can't change the data that would be returned from a query. All they do is stop people from accessing data based on the logic you supply.
If you apply the rule shown above, then any query where the user requests any document where their uid is not in participantsId, or they make a query that does not use their uid as a required filter on participantsId, then the query will simply fail. This is something that you should pretty easily be able to observe for yourself.
I suggest reading: What does it mean that Firestore security rules are not filters?

Best Practice to keep user data in firebase firestore?

I am using firebase as a backend for my Android App. And this app is a social media app where users can post, comment & like. so I am storing user data in user_collection and this collection is secured by security rules where if uid == auth.uid (any user can only access data of himself).
Now in this app when a user post something every user can see this post. And in post_collection I am saving userId in post_doc.
So the problem is I need to show name of user to other users and I have only userId but the problem is a user can't get name of other user by uid beacuse of security rules. now I have to solutions for this please tell me which one is better or you can also suggest any other solutions also?
I can use cloud functions getUserNameById() (Problem : I need to call this function very frequently in feed when user scroll)
I can store name also in post_doc (problem : when user changes his name then It will show old name in old post)
Thanks for you kind help
In a scenario like the one you describe, I would typically store the user name in each post doc. I would also ignore updates to the name, as I think of the user name in the post doc as a historical value: this is the name the user had when they posted this. Now you may want different behavior of course, in which case I recommend reading: How to write denormalized data in Firebase
Your approach with Cloud Functions is fine too, and quite common in some situations. But I tend to only use Cloud Functions for reading data, it the read operation itself is particularly complex, which isn't the case here. In a case like this, I'd recommend coming up with a data model that allows the use-case and security you want.
For example: if you create a collection usernames where each document has the UID as its document ID, and then contains a single field with the username for that UID, you could implement the lookup of the user name through Firestore.
So you could have:
Store the full user profile in /users/$uid.
Store the user name in /usernames/$uid.
Have a Cloud Function that triggers when /users/$uid is written and that updates /usernames/$uid.
The client then has read access to each /usernames/$uid document, or even to the entire /usernames collection in one go if needed.
This way the names can be cached on the client, and continue to work when the app is offline, unlike in your approach with a Cloud Function that looks up the user name.
Consider the solution: whatever public data you need (author name, author userpic link etc) just save it with the post at the time it had created.
So your Message Pojo will looks like:
id
authorName
text
etc..
and just display this name (authorName).
It will be the bad way to go any time to User_collection folder to take the name even if there are would be not strict security (becouse it takes time and document reads)

Custom Authentication in Google Firebase

I have a question regarding authentication using Google Firebase.
For an app, I want to build an authentication similar to the one Slack uses: first, the user provides the input as to which group they want to log in to. If there exists a group with the same name as provided in the input, the user is then taken to a login/signup screen.
I've thought about storing users in the realtime database as follows, but I think there must be a better way to do this (since I don't think I can use the firebase authentication in this case):
groups: {
"some_group_name": {
"users": [
"user1": {
.. user 1 information
},
"user2": {
.. user 2 information
}
],
"group_details": {
"name": ..,
"someGroupDetail": ..
}
},
"some_other_group_name": {
...
}
}
I haven't realized if there is an obvious answer yet, so I'm open to suggestions. How would you suggest I tackle this?
Thanks
PS: I'm building the application using Nativescript and Angular, and (so far) there is no server or database involved other than Firebase.
Another suggestion that might work, is by using Firebase Auth Custom Claims. That way, you only need to store the group ID and group name in your realtime database, without worrying to keep changing the database each time user is added or removed.
This is one way you can do it:
Store database exactly like you have it, with it's group ID and name.
In your backend script (I recommend Cloud Function), each time a User is registering themselves, add custom claims in your user: Specifying what group is the User belong to.
Every time user authenticate, retrieve the group ID from custom claims. And there you get it!
Note: be careful not to put too much information in your custom claims as it cannot exceed 1000 bytes.
Read more about it here: https://firebase.google.com/docs/auth/admin/custom-claims
I would suggest you to implement Root-level collections.
Which is to create collections at the root level of your database to organize disparate data sets(as shown in the image below).
Advantages: As your lists grow, the size of the parent document doesn't change. You also get full query capabilities on
subcollections.
Possible use case: In the same chat app, for example, you
might create collections of users or messages within chat room
documents
Based on the reference from the firebase cloud firestore
Choose a data structure tutorial (I know you are using Realtime database but structuring the database is the same since both are using the NoSQL Schema)
For your case:
Make 2 Collections: Users, Groups
Users: User info is stored in the form of document
Groups: In the Groups Collection, here comes the tricky part, you can either store all groups subcollection under 1 document or split into multiple documents (based on your preference)
In the group-subcollection, you can now store your group info as well as the user assigned where you can store user assigned in the form of array, therefore whenever a user access the group, query the user assigned first, if yes, then allow (assuming users can view all group)
You do the thinking now

Enforcing unique userIds in Firebase Firestore

I'm playing around with the new Firestore database from Firebase and was wondering if there was a more efficient way to enforce unique usernames (or unique child nodes).
I use email/password auth but allow users to create a unique handle, like Twitter.
With the Realtime database I was able to achieve this with transactions and a multiple node structure.
-users
-uid1234:
-username: ryan
-usernames:
-ryan: uid1234
I was thinking it may be possible to do this without the extra usernames node using documents as the username in Firestore.
You could do it probably do it in 2 ways. Check with a separate call before create user, if the user exist in an collection with usernames.
Also you could create a rule like:
match /users/{document=**} {
allow create: if !exists(/databases/$(database)/documents/usernames/$(request.resource.data.username));
}
In that case you get a error back if the username already exist on creating and that you can catch in the code.

Resources