Firestore rules validate query field value - firebase

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

Related

Firebase security rules based on custom user field

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.

Check References in Array - Firestore Security Rules

I am building firestore security rules for my app and in the events collection I have a property called members that has an array of references from the users collection. How would I go about making sure that the user that has sent the request is in that collection? I know I am able to get the userId through request.auth.uid but I'm unaware of how to get the document reference in firestore rules and make sure that the reference is in the array.
The answer that I have found is this:
match /events/{eventId} {
allow read: if /databases/$(database)/documents/users/$(request.auth.uid) in resource.data.members;
}
Looks like the in keyword allows me to check if a value is inside an array and /databases/$(database)/documents/users/$(request.auth.uid) creates a DocumentReference which is the data type stored in the array.
As clarified in this other post here, similar to yours, Firestore rules are not used for filtering data, but to set which data are accessible for which users and which queries can be performed.
Considering that, you will need to write code that will query and compare the datas from your request.auth.uid, with the ids from your subcollection. This way, you will be able to confirm the data you want, that is the user requesting being authorized to access the information. This would be the correct way to handle the request and return the information or not from your database.
A simple example of code that will confirm that the requesting users is in the members subcollection is similar to the following lines:
var user = firebase.auth().currentUser;
db.collection("events").where("members", "array-contains", user.uid).get()
While this code is untested, is a starting point for what you will need to do, to guarantee that the user requesting is allowed to retrieve the information. You can get more information on what you need here.
Let me know if the information helped you!

Firestore rule on uid doesn't work for unknown reason

I have an 'owner' field in my documents to entitle only the owner to read the document, and only a new document that its 'owner' field is the uid of the user, can be written:
allow read: if request.auth.uid == resource.data.owner;
allow create, write : if request.auth.uid == request.resource.data.owner;
The creation and update rule works as expected! I tested and saw that if the 'owner' field of the record in the new data is not the user's UID then it doesn't work!
The problem is the 'read' section. I wasn't able to read records. Only when I changed to allow read: request.auth.uid != null, I was able to read.
I triple checked that the records has an 'owner' field that is exactly the same as the UID, also in debug.
I'm have experience with Firebase, and I have no idea what is the problem here.
Since you indicate that "[You weren't] able to read records" (with an S at records), your problem most probably comes from the fact that security rules are not filters, as explained in the documentation:
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.
You don't show the query used to read the Firestore documents, but let's imagine your documents are in a collection named collection.
With a query like
query = db.collection("collection")
query.get().then(....);
you are querying independently of the User ID, hence the problem.
You need to adapt your query with the where() method, as follows:
//get the value of uid
query = db.collection("cities").where("owner", "==", uid)
query.get().then(....)

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.

Resources