Firestore security rules - trouble with permission to write to a collection - javascript

I've attempted to set up a security rule where a person writing a document to a particular collection may only do so if their uid matches the uid contained in the document, but can't get it to work. In this case I am writing a document to a collection called 'embedUsers' and the document being written contains a uid field that was acquired when the user account was created.
The rules are set up as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /embedUsers/{documents=**} {
allow read: if true;
allow write: if resource.data.uid == request.auth.uid;
}
}
}
The document is written by an Angular client, using the following code:
const userRef: AngularFirestoreDocument<UserModel> = this.afs.doc(`embedUsers/${user.accountAddress}`);
console.log("current logged in user", this.fireUser.uid);
try {
console.log(user.uid);
await userRef.set(user, { merge: true });
} catch (e) {
console.error(`FireAuthService.updateUserData unexpected failure with error ${e.message}`)
throw new Error("updateUserData failed");
}
console.log("wrote embed user successfully")
Although I have checked that the uid of the signed in user matches the uid field in the data object being written, this call to userRef.set fails with FireAuthService.updateUserData unexpected failure with error Missing or insufficient permissions. It seems like something must be wrong with my rules but I'm not sure what (if I change the rule to simply say allow write: if true; the document gets written as expected.

As explained in the doc, "When writing data ... the request.resource variable contains the future state of the document". So you should adapt your rule to:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /embedUsers/{documents=**} {
allow read: if true;
allow write: if request.resource.data.uid == request.auth.uid;
}
}
}

Related

Firestore rules 'Simulated read allowed' but fails in browser

My security rules are super simple. I have two collections - riders and races:
riders: can only be read from or written to when the user is signed in
races: can be read from by unauthenticated user; written to when user is signed in.
service cloud.firestore {
match /databases/{database}/documents {
// restrict read/write on all to authenticated
match /{document=**} {
allow read, write: if request.auth != null;
// then allow collection read if match
match /races/{id} {
allow read, list: if true;
}
}
}
}
These rules allow what appears to be the correct setup using the Firebase consoles Rules Playground but in the browser auth users behave as expected but the unauthed users are returned an error when making a call for a race:
core.js:6456 ERROR FirebaseError: Missing or insufficient permissions.
at new e (prebuilt-47338342-439a2133.js:188)
at prebuilt-47338342-439a2133.js:10415
at prebuilt-47338342-439a2133.js:10416
at e.onMessage (prebuilt-47338342-439a2133.js:10438)
at prebuilt-47338342-439a2133.js:10355
at prebuilt-47338342-439a2133.js:10386
at prebuilt-47338342-439a2133.js:15146
at ZoneDelegate.invoke (zone.js:372)
at Zone.run (zone.js:134)
at zone.js:1276
// service call
this.racesCollection = this.firestore.collection<Race>('races');
this.racesCollection
.doc(id)
.valueChanges()
.pipe(takeUntil(this.destroy$))
.subscribe((response: any) => {
console.log('=== APP SERVICE emits race ===', response);
this.race.next(response);
});
I've tried rewriting the rules but cannot seem to find my way around this one. Any help or ideas appreciated! Thanks.
You should write your security rules in such a way they overlap:
service cloud.firestore {
match /databases/{database}/documents {
// restrict read/write on all to authenticated
match /{document=**} {
allow read, write: if request.auth != null;
}
// then allow read for the races collection for all users
match /races/{id} {
allow read, list: if true;
}
}
}
In addition, note that list is a "sub-case" of read, so you might remove the list rule, i.e. just do allow read: if true;.
BTW, the simulator does correctly show that your rules do not allow reading a document in the races collection if you are not authenticated.

Firestore rules not allowing read given valid UID

I have written the following firestore rule to limit reads on the users collection by only allowing the request if the caller's UID matches the document ID. There is parity between the users collections' document ID and the UID in Firebase Authentication.
function isAuthenticated() {
return request.auth.uid != null;
}
// Users collection
match /users/{userId} {
allow read: if isAuthenticated()
&& request.auth.uid == userId;
}
I have a document in the collection with ID Eli90wRvWkfKcOfn1C4DBDqxQTz1. When hitting firestore with the same authentication user, I get the following error:
If I remove the check for the request.auth.uid == userId, it results in this rule and a successful read:
// Users collection
match /users/{userId} {
allow read: if isAuthenticated();
}
The query that is being called is:
export const getUser = (uid: string) => {
return db.collection('users').where('id', '==', uid).limit(1).get();
};
I've seen many people use the UID check in their rules but why isn't it working in my case?
Your security rules match this code:
db.collection('users').doc(uid).get();
So here we access a single specific documents, with the document ID being the UID of the user.
If you instead want to query for the UID in a field in the document(s), you can secure that with these rules:
match /users/{doc} {
allow read: if resource.data.id === request.auth.uid;
}
So now the rules match your original code, and only allow reading documents where the value of the uid field matches the uid of the current user.

Firestore returns insufficient permissions, even tough it shouldn't

I have the following rules set up for my Firestore database:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /collections/{document=**} {
allow read;
allow write: if isAdmin();
}
match /general/{document=**} {
allow read;
allow write: if isAdmin();
}
match /inquiries/{document=**} {
allow write;
allow read: if isAdmin();
}
match /orders/{document=**} {
allow write;
allow read: if isAdmin() || resource.data.userID == request.auth.uid;
}
match /products/{document=**} {
allow read;
allow write: if isAdmin();
}
match /users/{userId} {
allow write, read: if belongsTo(userId);
}
function belongsTo(userId) {
return request.auth.uid == userId
}
function isAdmin() {
return resource.data.admin == true;
}
}
}
As you can see, everybody is allowed to read /products and its documents plus subcollections. Which works for the products, but somehow the product's subcollection (every product has one called collection-colors) can't be read.
FirebaseError: Missing or insufficient permissions.
Code causing the error:
retrieveCollectionColors(name) {
this.db.collectionGroup('collection-colors', ref => ref.where('product', '==', name))
.valueChanges().subscribe( (val: []) => {
this.collectionColors.next(val);
}, error => {
console.log(error);
});
}
The rules you have right now don't apply at all to collection group queries. You'll need to write a special rule for that. From the documentation:
Secure and query documents based on collection groups
In your security rules, you must explicitly allow collection group
queries by writing a rule for the collection group:
Make sure rules_version = '2'; is the first line of your ruleset. Collection group queries require the new recursive wildcard {name=**}
behavior of security rules version 2.
Write a rule for you collection group using match /{path=**}/[COLLECTION_ID]/{doc}.
So, if you want to allow collection group queries for "collection-colors", it will look something like this:
match /{path=**}/collection-colors/{doc} {
allow read: ...
}
This will apply to all subcollections with the given name. You can't selectively allow or disallow subcollections based on the name of the parent collection.

Firestore security rules allow one document to be public

I'm working on an Angular app using Firestore here I have a bookings functionality. I want users to publicly read and write to bookings if there provided reference_no exists within one of the documents.
Here is how my document is structured:
These are my current security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /jobs/{job} {
allow read, write: if request.resource.data.property.reference_no == resource.data.property.reference_no;
}
match /{document=**} {
allow read, write: if isSignedInUser();
}
//functions
function isSignedInUser(){
return request.auth.uid != null;
}
}
}
This is how I'm querying this:
findByReferenceNo(reference_no): Observable < any > {
return this.afs
.collection(this.jobsCollection, ref => ref.where('property.reference_no', '==', reference_no))
.snapshotChanges()
.pipe(
map((actions) => {
return actions.map((snapshot) => {
const data = snapshot.payload.doc.data();
const id = snapshot.payload.doc.id;
return {
id,
...data
};
});
})
);
}
But not sure why I'm getting: Error: Missing or insufficient permissions.
Note: I'm not signed in while accessing this.
As far as I know, in order to guarantee query performance, Firestore security rules validate your query and not each individual document. This means that a query is only allowed if the rules can validate that the query won't retrieve more data than is allowed.
In your current model that is simply not possible, because it would require that the security rules check each individual document. For some examples of queries that can be secured, have a look at the Firestore documentation on securing queries.

Firestore publicly writable collection

When a user is created I need to add some additional data about the user (e.g. name).
So I need a publicly writable collection. However I've tried adding create and update (I think that's for set and add respectively), but I'm still getting "Error: Missing or insufficient permissions." when trying to do:
db.collection('newUsers').add({
firstName,
lastName,
});
With this rule:
service cloud.firestore {
match /databases/{database}/documents {
match /newUsers {
allow create, update;
}
}
}
How can I add an entry to that collection without being authed?
service cloud.firestore {
match /databases/{database}/documents {
match /newUsers/{user} {
allow create, update if true;
}
}
}
Note that you need to match an actual document, not just a collection. The wildcard {user} should do this.

Categories