Firebase database rules for group-shared data - firebase

My webapp, like 99% of apps, has in db a collection of users. I can access user at /user/ so I setup rule to allow every user to access only its data.
BUT I need every user join a group
/user/34029380432
- name: realtebo
- group_id: 123
- isAdmin: true
/group/123
- a lot of shared data, all members can erad, only isAdmin can write
How can I setup a rule that allow only members of each group to read it and only admin members to write group data ?!
Of course I could swap side, movin member list, as array, under each group and setting, inside each group, one or more admin id.
But I am not able to compose javascript rule.

I think what you could do is having a messages and users node within your group objects. You can access to parent object for that data node on your security rules and see if that user is a member of users object node by the use of exists()
/group/123
- messages // a lot of shared data, all members can read, only isAdmin can write
- users // list of user uids that are added to this group
- admins // list of admin uids
"group": {
"$groupId": {
"messages": {
".read": "data.parent().child('users').child(auth.uid).exists()",
".write": "data.parent().child('admins').child(auth.uid).exists()"
}
}
}

Related

Firestore Rules with multi-tenancy?

The Firebase Rules docs suggest building conditions comparing the authenticated user's token (i.e., request.auth) with the target Firestore document(s). Something like:
match /posts/{postId} {
allow read, write: if (request.auth.uid != null) &&
(resource.data.tenantId == request.auth.token.tenantId);
}
However, tenantId doesn't appear to be available in Firebase Rules like other related auth fields (e.g., uid, email, email_verified, etc.).
One option appears to be to add tenantId separately as a custom claim using the firebase-admin SDK. But that would create duplicate info on the user object:
{
uid: 'nzjNp3QIfSR6uWy',
emailVerified: true,
displayName: 'pickleR'
...
tenantId: 'wubalubadubdub',
customClaims: { tenantId: 'wubalubadubdub' },
}
An alternative option appears to be to create a tenants collection in Firestore. However, that approach seems to introduce needless complexity and inflate the # of required Firestore queries.
Are there alternatives for accessing the tenantId in Firestore Rules and/or alternative best practices for using Firestore with multi-tenancy?
Having gone down the custom claim route, I've then found the tenant Id is already stored in a nested object as 'request.auth.token.firebase.tenant'
In your example the rule would be:
match /posts/{postId} {
allow read, write: if (request.auth.uid != null) &&
(resource.data.tenantId == request.auth.token.firebase.tenant);
}
The two options you describe are the idiomatic ones:
Pass the information into the rules as part of the ID token as a custom claim.
Look up the information in the database from the rules by the request.auth.uid.
Neither of these is always better than the other: custom claims are more convenient and readable, while using the database is usually faster. It's common to use the database lookup for more volatile information, and claims for information that is "once".
Since this is for a tenant ID, which is unlikely to change quickly, I'd probably go for a custom claim.

Security Rules: Storing unique usernames in one document

On my app I am trying to make it so that users have to have a unique username.
My current method is to have a Social Collection with one document called Usernames. That document will store the users userID as the key for the field and then their username for the value.
I am struggling to write the correct security rules for this. I would like it so that:
All signed-in users can get this document
Users can only update their own data in the document, formatted as [theirUserId: theirUsername]
There can be no duplicate usernames, e.g.
userIdA: "foo"
userIdB: "foo"
At the moment the only point that I can't get to work is checking to see whether a username is already taken.
Another solution I have thought of is to reverse the fields (username: userID). But I can't figure out a way how to write the security rules for this method either.
Current Rules
// Usernames
match /Social/Usernames {
allow get: if request.auth.uid != null;
allow update: if isUserNameAvailable();
}
// Functions
function isUserNameAvailable() {
// This line works
return (request.writeFields.hasOnly([request.auth.uid]))
// This one doesn't
&& !(resource.data.values().hasAny([request.writeFields[request.auth.uid]]));
}
Firestore Data Structure
Any help is greatly appreciated, many thanks!

Firebase - Database : How to restrict access to sub-levels of other trees according to users

I am a bit stuck on my project.
I have a list of users which include the events allowed.
I have a list of events with users allowed.
I want to allow a user to get only the list of their events in "users".
And of course, I want the json sent to only include the the content of their events.
See the Database
See the Rules
Thank you for your help
If I understand correctly, you want to allow a user access (read/write ?) to a specific part of the your database/events.
According to the Firebase Secure Your Data docs, you can specify who gets what.
If you want the current user to be able to see the data in an event, I think this rule should work :
{
"rules": {
"events": {// allows read to /events
"$eventID": {
".read": "data.child('users').child(auth.uid).exists()",
".write": false
}
}
}
}
If you want the user to be able to edit the information as well, copy the rule from .read to .write.
As for sending the JSON of this event specifically, you need to do it with a db query to that specific node in the DB.

Do I need to do a get for every field of a document I want to access

I see that to get a field of a document in security rules one must use get. The example below shows getting the 'admin' field of some document in the users collection. If I wanted to get another field, would I have to do another get request or can I just do one get request and get all the fields I need in the document.
Here is the example I'm referring to in the documentation.
https://firebase.google.com/docs/firestore/security/rules-conditions
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// Make sure a 'users' document exists for the requesting user before
// allowing any writes to the 'cities' collection
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid))
// Allow the user to delete cities if their user document has the
// 'admin' field set to 'true'
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}
}
}
Yes, you would have to write another get(). There are no variables in Firestore security rules, so you can't store the contents of a get() in order to use its data multiple times.
Multiple gets accessing the same document might not incur multiple read charges. The documentation states:
Some document access calls may be cached, and cached calls do not count towards the limits.

Firebase JSON Security and Arrays

We'd like to use Firepad in our (mostly non-Firebase hosted) project, but we're having some troubles figuring out the best way to approach the problem.
Basically, we have many users, and each user can be a member of many groups. These "groups" each have their own Firepad which users can edit. We already have a deeply developed database structure using MySQL and don't really want to migrate our user data into Firebase right now, so we figured we'd get more creative.
We don't want users being able to edit the Firepads of groups they do not belong to. As such, as part of our authentication token, we figured we'd try sending along the user ID and the list of groups they belong to. Then, using the Firebase JSON security system, we could verify that the Firepad currently being edited is in the list of groups the user belongs to.
The problem is, the JSON system doesn't seem to accept many commands. There's no indexOf, and I can't call hasChild on the auth variable.
How can we ensure that users can only edit the Firepads of groups they belong to, without migrating all of our data to Firebase? (Or maintaining two copies of the database - one on MySQL and one on Firebase)
The trick here is to use an object instead of an array to store the groups (a tad awkward, I know. We'll try to make this easier / more intuitive). So in your auth token, you'd store something like:
{ userid: 'blah', groups: { 'group1': true, 'group2': true, ... } }
And then in your security rules you could have something like:
{
...
"$group": {
".read": "auth.groups[$group] == true",
".write": "auth.groups[$group] == true"
}
}
And then a user will have read/write access to /groups/<group> only if <group> is in their auth token.

Resources