Firestore security rules and vuefire - firebase

i have the following sample app here: Github repo
It uses vuefire in ChatList.vue
// vuefire firestore component manages the real-time stream to that reactive data property.
firestore() {
return {
chats: db.collection('chats').where('members', 'array-contains', this.uid)
}
},
I now wrote security rules to secure the data, but can't seem to get the combination of vuefire and security rules to work:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
// THIS IS THE PART I'D LIKE TO REMOVE
match /chats/{chatId=**} {
allow read: if request.auth.uid != null;
}
// THIS WORKS AS INTENDED, AND I'D LIKE TO INCLUDE "READ"
match /chats/{chatId}/{documents=**} {
allow write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
So the goal is: make the individual chats only readable and writable to users that are in the members array in firestore. (Currently i achieved this partially, since all chats are readable to anyone, but only writable to users in the members array.)
Do i have to rewrite the vuefire component so i can have the following security rule? (It gives an error message: listing of chats not possible due to missing permissions)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /chats/{chatId}/{documents=**} {
allow read, write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
For completeness, the working solution is (credits to Renaud Tarnec):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /chats/{chatId=**} {
allow read: if request.auth.uid in resource.data.members;
}
match /chats/{chatId}/{documents=**} {
allow read, write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}

Since you want to check, in your Security Rules, if a given value (the user uid in this case) is contained in a field of type Array in your document, you can use the in operator of the List type.
So, the following should do the trick:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
// THIS IS THE PART I'D LIKE TO REMOVE
match /chats/{chatId=**} {
allow read: if request.auth.uid in resource.data.members;
}
// ....
}
}

Related

How to prevent Firestore to create root collection automatically during addDocument?

In Firestore, I have a collection "form1"
In my client app, when I create a document in a collection that doesn't exist, say "form2"
db.collection("form2").addDocument(data: data)...
I see from Firestore console, "form2" was created automatically to hold the new document. I hope addDocument() would return error in this case.
How to do it with Security rules? or with other method?
Here is my current Secuirty rules:
rules_version = '12';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} { // only logged-in user can access
allow read, write: if request.auth.uid != null;
}
}
}
Why can't following work? (insufficient permission even if the root collection exist)
service cloud.firestore {
match /databases/{database}/documents {
match /{collection} {
allow read, write: if false;
}
match /{collection}/{document} {
allow read, write: if exists(/databases/$(database)/documents/$(collection));
}
}
}
You can disallow writing to all documents as default and then write rules to allow the only ones you decide:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;// This disallow write and read for all documents
}
match /admin_/** {
allow read, write: if request.auth.token.admin == true;
}
}
}
This will not allow writes to any collection or document except to admin

Firebase FireStore Abstracted function not working

I have a working read rule in fireStore to check that a user is in the users array of the /accounts/{account} resource:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /accounts/{account} {
allow read: if request.auth != null && request.auth.token.email in resource.data.users;
}
}
}
In order to simplify the code and to use a function elsewhere to check user access based on a resource id, and following the information in this link: Security Rules! 🔑 | Get to know Cloud Firestore #6 19:25 I have attempted to abstract the code:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasUserAccess(account){
return request.auth.token.email in get(/databases/$(database)/documents/accounts/$(account)).data.users;
}
match /accounts/{account} {
allow read: if request.auth != null && hasUserAccess(account);
}
}
}
So that when I want to cross-reference the access for related documents, I can call the function. Why does the abstracted version fail to work? It seems like it should be working correctly based on the youtube video.
You will need to use hasAny() to check if the users list of accounts contains the email passed.
Also note that you have to pass the request.auth.token.email in the function.
Please use the following code in order to see if the user exists in the field "users" of your other collection:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasUserAccess(account,email){
return get(/databases/$(database)/documents/accounts/$(account)).data.users.hasAny([email]);
}
match /accounts/{account} {
allow read: if request.auth != null && hasUserAccess(account, request.auth.token.email);
}
}
}
This works, it seems I couldn't use the function within the collection the function was inspecting?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasUserAccess(account){
return request.auth.token.email in get(/databases/$(database)/documents/accounts/$(account)).data.users;
}
// You can't use the function within this match condition
match /accounts/{account} {
allow read: if request.auth != null && request.auth.token.email in resource.data.users;
}
// You CAN use the function within this match condition as it isn't for the collection /accounts
match /otherData/{account} {
allow read: if request.auth != null && hasUserAccess(account)
}
}
}

Firestore Rules

I have the following rules in my Firestore database. But I still keep getting a notification from Firestore that the rules I set in my database are not secure. Please see the codes below. Any suggestions or recommendations to make the database more secure?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if userIsAdmin();
}
match /Basket/{Basket} {
allow read, update, delete: if userOwnPost();
allow create: if request.auth.uid != null;
}
match /AllOrders/{AllOrders} {
allow read, create, update: if userOwnPost();
}
match /Items/{Items} {
allow update: if userOwnPost();
}
match /Voucher/{Voucher} {
allow update: if userOwnPost();
}
match /User/{User} {
allow read, update: if userOwnPost();
allow create: if request.auth.uid != null;
}
function userIsAdmin() {
return getUserData().userRole == 'Admin';
}
function getUserData() {
return get(/databases/$(database)/documents/User/$(request.auth.uid)).data;
}
function userOwnPost() {
return getUserData().objectId == request.auth.uid;
}
}
}
You have some overlapping match statements in your rules:
With
match /{document=**} {
allow read: if true;
allow write: if userIsAdmin();
}
you allow read access on all documents in your Firestore database.
As explained in the doc (section "Overlapping match statements"), "in the case where multiple allow expressions match a request, the access is allowed if any of the conditions is true".
So all your other security rules are just overlapped by this one.

firebase firestore rules authenticated access to all collections except one

I have the following firestore structure, basically 3 collections
publicdata
protecteddata1
protecteddata2
I want to have protecteddata1 and protecteddata 2, and really the entire firestore database as authenticated users only.
But i want the public to have readonly access to 'publicdata' collection..
The following is my attempt but it doesn't work
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if (request.auth.uid != null);
}
match /publicdata {
allow read;
}
}
}
You can use the following functions I created to do this
function isUserAuthenticated() {
return request.auth.uid != null;
}
You can then use it like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isUserAuthenticated();
}
match /publicdata/{itemId} {
allow read : if true;
allow create : if isUserAuthenticated();
allow update: if isUserAuthenticated();
allow delete: if isUserAuthenticated();
}
/* Functions */
function isUserAuthenticated() {
return request.auth.uid != null;
}
}
}
As others have explained, if you have multiple matches for the same document they are OR'ed together, so you can't implement an exception with that.
What you can do though is capture the collection name in a variable, and then implement the exception in a single match:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{collection}/{document=**} {
allow read: if collection != 'publicdata';
allow write: if (request.auth.uid != null);
}
}
}
So here we allow reads from all collections except publicdata.
Because here is says:
Overlapping match statements
It's possible for a document to match more than one match statement. In the case where multiple allow
expressions match a request, the access is allowed if any of the
conditions is true: ...
You can use this:
rules_version = '2';
service cloud.firestore {
// Check if the request is authenticated
function isAuthenticated() {
return request.auth != null;
}
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isAuthenticated();
}
match /publicdata/{document=**} {
allow read: if true;
}
}
}

Firestore rules on subcollection

I have a "game" collection on firestore with a "levels" sub-collection. I'm trying to set-up the security rules so that you can only access game or level you created.
All documents (games or levels) have an authorId field with the uid of the user that created them. I have try this rule but still got an Missing or insufficient permissions error:
service cloud.firestore {
match /databases/{database}/documents {
match /games/{document=**} {
allow read, write: if document.data.authorId == request.auth.uid;
}
}
}
What am I missing?
I have tried the following rules too with no success:
service cloud.firestore {
match /databases/{database}/documents {
match /games/{game}/levels/{level} {
allow read, write: if level.data.authorId == request.auth.uid;
}
}
}
service cloud.firestore {
match /games/{game} {
allow read, write: if game.data.authorId == request.auth.uid;
match /levels/{level} {
allow read, write: if level.data.authorId == request.auth.uid;
}
}
}
According to the reference documentation, resource is the object that contains the document data that the user is trying to write. You use its data property to get a hold of its field values.
service cloud.firestore {
match /databases/{database}/documents {
match /games/{document=**} {
allow read, write: if resource.data.authorId == request.auth.uid;
}
}
}
match /databases/{database}/documents {
match /users/{uid} {
allow read, write: if request.auth.uid == uid;
}
match /data/{uid}/temperature
/{document=**}{
allow read, write: if request.auth.uid == uid;
}
match /data/{uid}/blood_pressure
/{document=**}{
allow read, write: if request.auth.uid == uid;
}
}
}
I did this to access subcollections "blood_pressure" and "temperature" for only authenticated users. It works fine for me.

Resources