Firestore security rules, get() and the allow create operation - firebase

I've made an app where users can post records to the firestore database. I'm now at the point where I'm implementing security rules but I'm struggling to find a solution.
My code looks like this
docRef.get().then(function(doc){
if(doc.exists){
docRef.set(//data to set here)
} else {
docRef.update(//data to update here)
}
.catch((error) => {
alert('Error' + error);
})
My rules are currently set to allow the user to create an object if they are authorised, and only allow an update if the user id on the record matches themselves.
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user}{
allow create: if request.auth.uid != null;
allow read, write: if request.auth.uid == resource.id;
}
match /equipment/{document} {
allow create: if request.auth.uid != null;
allow read, update: if request.auth.uid == resource.data.user;
}
I think the problem lies in that I'm trying to get the object prior to creating it. However I need to perform these checks prior to writing to the document.
Am I right in thinking this the problem? and if so is there a solution I can implement.
Thanks

I'm still working on my rules, but I hope this helps.
If you're trying to make sure the user is editing something they own and their uid is the same as the document id....
function isOwner(userId) {
return request.auth.uid == userId
}
match /users/{userId} { //this is the document
allow write: if isOwner(userId);
}
If you're trying to make sure they are the creator of a document:
match /equipment/{documentId} {
allow create: if request.auth.uid != null;
allow read, update: if get(/databases/$(database)/documents/equipment/documentId).data.userId == request.auth.uid;
}
https://firebase.google.com/docs/firestore/security/rules-conditions?authuser=0
You need to use the get function to retrieve a document you're interested in. That function returns data that has the related fields you can compare to, in this case, whatever field name you stored the user ID on so you can compare it to their auth.uid.

I think you should change the 'create' rule as following:
allow create: if request.auth.uid != null &&
request.auth.uid == request.resource.data.id;
This is what documentation says: "if your ruleset allows the pending write, the request.resource variable contains the future state of the document."

Related

Firebase Security Rules always false on get() or exists() operation PERMISSION_DENIED

I finished making my app and I'm implementing the security rules.
I am currently trying to validate whether or not a user exists in the bank, if he exists, he will be able to read and write to a specific path. I'm using Firestore:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents{
match /Usuarios/{documentID} {
allow read, update, delete: if request.auth.uid != null && request.auth.uid == documentID;
allow create: if request.auth.uid != null;
match /Manejo/{manejoID} {
allow write, read: if exists(/databases/$(database)/documents/$(request.auth.uid)); // The id is never exist... even when it does exist in my database, this operation simply DOES NOT WORK despite the document with the Uid existing in the database
}
}
}
Edit1:
match /Manejo/{manejoID} {
allow write, read: if true;
}
This condition not work too...
You need to declare in which collection you want to check the existence of the doc with docId == userId
With
allow write, read: if exists(/databases/$(database)/documents/$(request.auth.uid));
you don't declare the collection.
Look at the following example in the doc, which checks for the existence of the doc with docId == userId in the users collection. See how /users/ is added right after $(database)/documents.
allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))
I understand that want to check the existence of the doc in the Usuarios collection. If this assumption is correct, do as follows:
allow write, read: if exists(/databases/$(database)/documents/Usuarios/$(request.auth.uid));
Note that you may be interested by this Firebase blog post.

Firestore security rules to compare two databases and prevent duplication

In my app I want to be able to have books assigned to a reader without there being any duplicates. Can I assign a rule like that to firestore or is that something to handle in my swiftui code? The security rules so far is as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match/users/{uid}{
allow read: if request.auth.uid == uid
allow create
}
match/books/{id}{//display library if user logged in
allow read: if request.auth != null
}
match/reader/{uid}{//check for readers assigned to user
allow read: if request.auth != null && request.resource.data.uid == request.auth.uid;
allow write: if request.auth != null;
match/reader_books/{id}{
allow read: if request.auth != null
allow write:if request.auth != null
}
}
}
}
EDIT: I tried to create a function in swiftui code to check for duplicates here:
func checkData(_ readerBooks: ReaderBooks){
print("reader ID : \(readerBooks.readerID)")
let checkBooks = db.collection("reader_books").document("\(readerBooks.readerID)")
checkBooks.getDocument{(document, error) in
if let document = document, document.exists{
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
}else{
print("Document does not exist")
self.postData(readerBooks)
}
}
}
When trying to add a book to a reader it always returns document does not exist in the console even when the readerID is present in the document's field. Is there an issue with the code somewhere?
You don't need security rules for this. In the reader_books subcollection, just use the same ID as the book from the books collection. Since a collection can't have two documents with the same ID, it will be impossible for one book to appear in that collection twice.

Setup Firestore User Security Rule

I am new to Firestore and I'm trying to setup simple security rules so that only someone who is signed in can create a new database entry and that users can only read and write their own entries.
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{userID} {
// Can only create a new entry if signed in with a uid.
allow create: if request.auth.uid != null;
// Can only update an entry if signed in with uid and changing own information (saved under uid)
allow update: if request.auth.uid != null &&
userID == request.auth.uid;
// Can only read (get/list) an entry if signed in with uid and reading own information (saved under uid)
allow read: if request.auth.uid != null &&
resource.data.userID == request.auth.uid;
}
}
}
The create new entry case works fine, but I wonder if this is secure enough.
For updating and reading, I also want to check that the user is updating/reading their own entry. The document name is the uid (in other words UserID) from Firebase so simply checking that request.auth.uid is the same should do the trick, but something is off in the way I'm writing it. The call gets blocked and when I run it in the simulator I get the error: Missing or insufficient permissions. I can't figure out after reviewing the documentation and this tutorial video.
Something like below should be sufficient for your situation:
// True if the user is signed in or the requested data is 'public'
function signedInOrPublic() {
return request.auth.uid != null || resource.data.visibility == 'public';
}
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if signedInOrPublic();
}

Firestore where clause with security rules

Suppose there are the following rules in place
service cloud.firestore {
match /databases/{database}/documents {
function roomAccess() {
return resource.data.allow_anonymous == true || (request.auth.uid == resource.data.uid);
}
function writeOnlyOwner() {
return request.auth.uid == resource.data.uid;
}
match /rooms/{room_id} {
allow read: if roomAccess();
allow write: if writeOnlyOwner();
allow create: if request.auth.uid != null;
}
}
}
Also, this is what an document in the collection rooms looks like
Question
Is there any way to query the specific room where the access_key is set to some value?
db.collection('rooms')
.where('access_key', '==', access_key)
.get()
It is important to note that i'm querying as the creator of the room (and the room.uid with match my uid
Nonetheless, it fails on access rights.
And the documentation doesn't seem to describe such behavior.
As far as I understood, the query fails because there might be some rooms, with this access_key, for which roomAccess() would fail.
But I can't seem to find a way around it.
My problem was that I should have been checking for
resource.data.allow_anonymous == true || request.auth.uid != null since I wanted to get the room either if the user is authenticated or it is public.
In this case, all the documents retrieved by access_key query match the security contraints

Cloud Firestore Security Rules documentation example

This documentation page: Writing conditions for Cloud Firestore Security Rules, says the following:
Another common pattern is to make sure users can only read and write their own data
And provides this example:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
I don't understand why the create rule is not defined with the same condition as the rest, if request.auth.uid == userId, but instead is defined with if request.auth.uid != null. As I understand it, with this rule any user can create any document inside users, but cannot do anything with it unless it matches his uid. So why allow it at all?
Let's talk about the very basic security rule that could be implemented (with user authentication):
allow read, update, delete: if request.auth.uid != null;
allow create: if request.auth.uid != null;
where any user can delete the documents of another people creation. So to restrict/control it, we implement as shown in the code snippet provided.
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
The code snippet is just an example use case that uses different conditions for reference purposes as this is a tutorial/guide, so the Firebase team try to fit as many possible conditions to the code snippet.
You can, of course, do allow create: if request.auth.uid == userId; to strictly restricted to that particular user.
I hope it gives you some idea!

Resources