Firebase security rules based on custom user field - firebase

I'm trying to set up security rules (use Firebase Cloud Firestore).
I changed the "users" table (added company_id field) and create "appointments" table (with company_id). I want to implement the following functionality (when a user requests appointments, he only receives appointments with his company id)
Wrote a rule:
match /appointments/{appointment} {
allow write;
allow read, update, delete: if resource.data.company_id == get(/databases/$(database)/documents/users/$(request.auth.uid)).data.company_id;
}
But my code throw error about permissions
const q = query(collection(db, 'appointments'), where("company_id", "==", company_id), orderBy("createdAt"));

Firestore doesn't magically know what documents have what company_id, so it tries to read all documents to return the documents that you want, but it can't, your security rules stop it.
Either get rid of the security rules, or find a way to structure your database so that you don't have this problem.
I don't know enough about your database to tell you a good structure, but what about something like this?
A collection called companies.
Each document in the collection is a company_id
A subcollection called users, where you put all users that belong to this company.
Another subcollection, called appointments, where you put all appointments for the company.
That way, you could write a security rule like so:
match /companies/{company}/appointments/{appointment} {
allow create: if true;
allow read, update, delete: if exists(/databases/$(database)/documents/companies/$(company)/users/$(request.auth.uid))
}
The problem with this approach is that, because we have divided all users and appointments into different collections, it will be impossible to query for all existing users or for all existing appointments.
It all depends on your use case.

Related

Firestore rules validate query field value

I have collection of users and every user can search for others by their publicId
query(
collection(db, "users"),
where("publicId", "==", "..."),
limit(1)
);
and I want to allow users to regenerate their `publicId"s so others won't be able to find them by the old ones.
The problem is that if someone finds a user once and get their doc id they could potentially get the user by that doc("users", "docId") regardless of their "publicId".
I tried to use request.query.publicId == resource.data.publicId, but query seems to only provide limit, offset and orderBy.
Is there a different way to access the query field value or a different way to mitigate the issue?
For the public profile, it might be best to create another collection e.g. "public_users" where the document ID is user's publicId. So when a user regenerates their ID, you can just create another document with new publicId and then delete the previous one.
Do not store a reference to user's UID in this document if you want to keep that a secret. Instead, store this public ID in the "users" collection so that can be read by user only.
Alternatively, you can make your requests through a Cloud Function and block direct requests to Firestore. So there's no way anyone can query by user ID.
For the main collection, you can add a rule that allows users to write their own document only like this:
match /users/{userId} {
allow write: if request.auth.uid == userId;
}

Trying to separate the data by user uid firebase?

I have this in my rules but it's not working, just started using firebase a few weeks ago
match /pets/{owner} {
allow read, write: if request.auth.uid == owner
}
this is the code im using to get the data from the collection:
Stream<List<Pet>> get pets{
print(userUid);
return petsCollection.where('owner', isEqualTo:
userUid).snapshots().map(_petListFromSnapshot);
}
im using the print to see if i get the uid from the current user for that query, but it comes null. In a diffrent part of the code i already add the uid of the person that creates a document to the collection.
what im trying to say is that i need advice on how to only get the current user data.
You cannot use security rules to filter results
Once you secure your data and begin to write queries, keep in mind that security rules are not filters. You cannot write a query for all the documents in a collection and expect Cloud Firestore to return only the documents that the current client has permission to access.
https://firebase.google.com/docs/firestore/security/rules-conditions?authuser=0#rules_are_not_filters
Use filter to get current user related documents.

How do I securely limit users to only list documents that they've created?

I want to be able to limit users to only list documents they've created.
The user id is stored in the user field
Obviously I can do
db.collection('projects').where('user', '==', firebase.auth().currentUser.uid)
.. but any tech savvy user could just remove the filter and get everything.
I've limited access in rules like
match /projects/{project} {
allow read,update: if request.auth.uid == resource.data.user;
allow create;
}
But this doesn't work, you can't list at all.
Is there a way of doing this without creating a subcollection of the user's entry in the user collection? I'd really prefer to have them all in one place.
Surely this is an extremely common scenario.
Assuming you stored the user_id in the field "user" in firestore. You can use
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
db.collection("projects").whereEqualTo("user", uid)...
After ellipses you can use .get() with onSuccesss or onComplete or add Snapshot listener.

How to list all documents the user can read from a Firestore collection?

How do you get all documents in a collection, for which the current user has read permissions?
Trying to get all documents results in a permissions error, because it includes attempts to read documents where the user does not have permission (rather than returning the filtered list of documents).
Each user in this app can belong to multiple groups. Reads are locked down to the groups that they have been added to.
match groups/{group} {
allow read: if exists(/databases/$(database)/documents/groups/$(group)/users/$(request.auth.uid));
}
Here's how this would look with a hypothetical subcollection-contains-id operator.
firestore()
.collection("groups")
.where("users", "subcollection-contains-id", user.uid);
As a temporary workaround I've moved this logic to a cloud function. Here's a shorthand of how it works.
for (let group of firestore().collection("groups")) {
let user = firestore.doc(`groups/${group.id}/users/${uid}`);
if (user.exists) {
// Send this group id to the client
}
}
How can I keep these concerns together and move this logic to the client side without relaxing the security rules?
You could add owners field in the documents inside a collection
owners: ["uid1", "uid2"]
Then, you could get all the posts with uid by searching with array_contains
ref.where("owners", "array-contains", uid)
In rules, you could add sth like these:
allow read: if request.resource.data.owners.hasAny([request.auth.uid]) == true
allow update: if request.resource.data.owners.hasAny([request.auth.uid]) == true

Firestore permissions and rules for querying collections (list) that belong to the logged-in user

I'm building a contact manager where a user has a bunch of "contacts" in their address book. I only want the user who created the contact to be able to query that contact. I wrote a query below that says what I want it to do, but the query does not work and I do not know why.
All contacts are created with an owner_id field that corresponds to the uid of the user that created the contact.
service cloud.firestore {
match /databases/{database}/documents {
match /contacts/{contactId} {
// only allow read for contacts if the current user is the owner
allow read: if request.auth.uid == resource.data.owner_id // <-- this does not work
allow write: if request.auth.uid != null && request.resource.data.owner_id != null;
}
}
}
When I run the query, I get nothing back, and the simulator does not allow me to run queries on list queries for the entire collection, only get for a single document. The query is simply:
db.collections('contacts')
I've also tried limiting using a where clause:
db.collections('contacts').where('owner_id', '==', <hard-coded-owner-id>)
I should note that when I query for a single document, the syntax above does appear to work. It just appears to fail when I query a collection.
So my question is, how does one write a database rule such that I can list all items in the collection while only returning the items that are associated with the logged-in user?
https://firebase.google.com/docs/firestore/security/get-started
I would expect your first query to fail because it's essentially trying to access documents that it doesn't have permission to read. Your rules will not implicitly filter the results.
I'd expect your second query to work because it's only accessing documents that are allowed by permissions. However, it will only work when the effective UID as reported by Firebase Authentication is the same as the one you hard coded. That's what you're rule is verifying - that the logged in user is only trying to read documents where they are present in owner_id. If you're working in the console simulator, you will have to turn on Authentication and put the right UID in the form.

Resources