Firestore security rules to compare two databases and prevent duplication - firebase

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.

Related

How to write security rules for users setting documents that don't exist?

When a user registers, a document should be set in Firestore (database/users/${uid}). However, I keep getting a "Missing or insufficient permissions." error.
This is the most relevant security rule
match /users/{documents=**} {
allow read, create, update: if request.auth != null && request.auth.uid == resource.id
}
Another rules I tried implementing was
match /users/{uid=**} {
allow read, create, update: if request.auth != null && request.auth.uid == uid
}
and this is the code that registers the user and sets the document
createUserWithEmailAndPassword(auth, emailInput, passwordInput).then(
(UserCredential) => {
console.log(UserCredential);
uid = UserCredential.user.uid;
sendEmailVerification(auth.currentUser).then(() => {
toast.success(
"Account created and email verification sent! Please check your inbox/spam folder",
{
duration: 10000,
}
);
setDoc(doc(db, "users", uid), {
userSettings: ["example", 0],
});
router.push("/verify");
});
}
);
Why can't I set a document as an authorized user accessing my own user document?
The problem is request.auth.uid == resource.id. From the documentation,
The resource variable refers to the requested document, and resource.data is a map of all of the fields and values stored in the document.
But the document does not exists as user has just registered to your application.
Try the following rules instead:
match /users/{userId} {
allow read, create, update: if request.auth != null && request.auth.uid == userId;
}
This rule will ensure that a user can create/read/update a document with their user ID only.
Also do note that the match path is /users/{userId} and not match /users/{userId=**} as in your question. The value of userId would be /userID and not just userID if you use the recursive wilcard (=**) and rule will fail always.
If the rule must be applied for all nested collections, then use the recursive wildcard on the next path segment:
match /users/{userId}/{path=**} {
// ... can still read userId
}

Firebase Security Rules -- using attribute from User document

I'm struggling to make my Firebase security rules work.
I only want to allow write access to documents in the collection Nouns to users with the attribute 'admin' == true (boolean) in their user document (stored in collection Users).
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{userId}/{documents=**} {
allow read, write: if request.auth != null
&& request.auth.uid == userId
}
match /Nouns/{documents=**} {
allow read: if request.auth != null
}
match /Nouns/{documents=**} {
allow read, write: if request.auth != null
&& resource.data.admin == true
}
}
}
The rules playground gives the following error when I try a write on a document in the Nouns collection:
Error: simulator.rules line [17], column [13]. Property admin is undefined on object.
Can anyone please let me know what I'm doing wrong?
resource.data will contain data of the document being accessed in Nouns collection. If you want to read data from user's document, use get() instead:
match /Nouns/{documents=**} {
allow read, write: if request.auth != null
&& get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.admin == true
}
Collection names are case-sensitive so make sure you enter it correctly.

How to manage rights in firebase to allow differents users to read/upate/write?

I've a firestore database and I now need to add a new collection.
Each entry of this collection should contain:
Which userId is the owner(field admin)
Which userId has been allowed to edit this element(field writer)
Which userId has been allowed to only read(field reader).
I'm currently only at the first step, and already strugling:
I was hoping to be able to query my collection( /trips/) and get only the one that I'm allowed to access, but I get an error:
FirebaseError: Missing or insufficient permissions.
Here is my rules file:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
match /trips/{trip} {
allow read, update, delete: if request.auth != null && request.auth.uid == resource.data.admin;
allow create: if request.auth != null;
}
}
}
So my questions:
Is this the correct way of managing resource that must be acceeded by multiple people(meaning, I cannot just have the userId in the path since there are multiple users)
How should I query only the documents list that I'm allowed to see?
Thank you very much for your help
As you will read in the doc, "All match statements should point to documents, not collections".
With
service cloud.firestore {
match /databases/{database}/documents {
match /trips {
// ....
}
}
}
you don't point to a document. You should use a wildcard to point to any document in the specified path, as follows:
service cloud.firestore {
match /databases/{database}/documents {
match /trips/{trip} {
// ....
}
}
}
Therefore the following should correctly implement your requirements:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /trips/{trip} {
allow read: if request.auth != null &&
(request.auth.uid == resource.data.admin
|| request.auth.uid == resource.data.writer
|| request.auth.uid == resource.data.reader
);
allow update: if request.auth != null &&
(request.auth.uid == resource.data.admin
|| request.auth.uid == resource.data.writer
);
allow create: if request.auth != null;
}
}
}
Then, for the two questions:
Is this the correct way of managing resource that must be acceeded by multiple people (meaning, I cannot just have the userId in the path
since there are multiple users)
If the admin, writer and reader are specific for each document, yes this is the correct way. If those roles would be more global (e.g. all the trips to Europe can be edited by the same user), you could use a role based approach with Custom Claims.
How should I query only the documents list that I'm allowed to see?
It is important to note that rules are not filter. So your query for getting docs needs to be aligned with the rules. In your specific case, you could have an additional field of type Array which contains three values; the uids of the admin, writer and reader, and use the array-contains operator. Something like:
const user = firebase.auth().currentUser;
const query = db.collection("trips").where("authorizedReaders", "array-contains", user.uid);
match /{document=**} {
allow read, write: if false;
}
You don't need the above code as it will apply to all routes of the database, because of the above line you are getting the below error as it does not allow you to read and write to the database
FirebaseError: Missing or insufficient permissions.
Now, if you want to assign privileges to users then you should add the Role field to users collections which would have a value such as Admin, Editor, Reader
Then, you can check in routes something like below
match /users/{userId}/trips/{tripId} {
allow read, delete: if request.resource.data.role == "Admin";
allow create, update: if request.resource.data.role == "Admin || request.resource.data.role == "Editor";
}
If you want to know more about how to create a route check out this video for the best explanation

Firebase security rules syntax with arrays/list property

Within a Firebase Firestore collection with path 'organizations' each document contains a list of string userID's of users who can update or delete that document.
export interface Organization{
name?: string,
owners: string[]
}
I would like to create a Firebase security rule that ensures that only a logged in user with a uid that is in this list can edit or delete the object. Unsure of the appropriate syntax.
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{organization} {
allow read: if true;
allow create: if request.auth != null;
/// What should be the syntax here?
allow update, delete: if request.auth != null && (request.auth.uid in resource.data.owners); // <--------- What should be the syntax for this line?
}
Ok, answering my own question here in case it's useful for anyone else.
It looks like the 'in' syntax above actually works even-though it was a complete guess and I wasn't able to find any documentation for it in the firebase security roles documentation.
Final code:
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{organization} {
allow read: if true;
allow create: if request.auth != null;
allow update, delete: if (request.auth != null) && (request.auth.uid in resource.data.owners);
}

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

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."

Resources