9
votes

In the Firestore security rules, is it possible to check certain document fields when using a list query?

Using Angular, I want to retrieve a single document from the userprofiles collection using its username property as follows:

let userprofile = this.afs.collection( 'userprofiles', ref => 
ref.where('username', '==', username ).limit(1);

I want this query to be allowed by the Firestore security rules, if either:

  • the userprofile is published, or
  • the userprofile is unpublished, but the corresponding user is logged-in

Here are my Firestore security rules:

match /userprofiles/{userprofileId} {

    allow list:  if( resource.data.published==true || (    
                     resource.data.published==false &&
                     resource.data.uid==request.auth.uid )
                 );
    }
}

For context, I am using the exact same rule to allow get request, which works fine. However, the query in the above example causes a list request, not a get. And in that case, these rules do not permit the query. I am getting Error: Missing or insufficient permissions.

I remember reading something along the lines of that for list queries, the rules must allow either all or no documents, which in my case doesn't apply. So I sort of understand why it doesn't work.

My question is, can I change something to make it work for my query? Or is this not possible? Any ideas for workarounds? (apart from the obvious "query by document-id" or "make username the document-id")

2
Did you solve this issue? I have run into a case where i can get the document directly via id, but when I do a where query that checks for id == MYID (which is a list with one result) it fails with a permission denied. - Tom

2 Answers

12
votes

In order to allow "list" for queries, the query parameters must match the security rules. Please refer to the documentation.

Your query would need to include the published==true statement:

let userprofile = this.afs.collection( 'userprofiles', ref => 
ref.where('published', '==', true).where('username', '==', username ).limit(1);

And your rules could be simplified to:

match /userprofiles/{userprofileId} {

    allow list: if resource.data.published==true || 
                   resource.data.uid==request.auth.uid;
}

Note that the query would only list the published userprofiles. I don't think it would be possible to query for "published==true" OR matching the uid condition at once, your code would need to query twice.

-6
votes

My be late, but statement like this works in my case

match /userprofiles/{userprofileId} {
    allow read: if resource.data.published==true;
    }
}
match /userprofiles/{userprofileId} {
    allow read: if resource.data.uid==request.auth.uid;
    }
}