1

I'd like to query a Firestore collection based on a string field, returning all documents where that field starts with a custom claim on the user's auth token. I was able to use this answer to create a query using >= and < operators with a successor key, and this works well for me when my I relax my Firestore rules.

I'd like to lock down my ruleset so that a user only has access to documents that start with their custom claim.

I've read the rules are not filters literature, and from my understanding of it, I just need to write rules such that my query can never return data that would violate the rules.

So that's what I'm attempting to do without much success:

I have a Firestore collection with documents that look something like this:

{
   "id": 1,
   "namespace": "foo.bar"
}

Each user has a custom claim on their auth token, let's say it's my_namespace.

I wrote a Firestore rule like so:

function hasNamespaceAccess(request, resource){
        //allow if data.namespace starts with request.token.my_namespace
        return resource.data.namespace.matches(request.auth.token.my_namespace + ".*");
    }


      match /path_to_my_objects/my_collection/{my_obj} {
        allow read, write: if hasNamespaceAccess(request, resource);
    }

My query, after simplifying to make this post as concise as I can, looks like this:

return db
        .collection('my_collection')
        .where('namespace', '>=', 'foo.') //the namespace "query values" are hard coded for clarity here
        .where('namespace', '<', 'foo.c')

The token which is used when making a call to Firestore does, for sure, have "my_namespace": "foo"

What I Expect

My Firestore rules says that a user has access to any document where namespace starts with "foo" -- the doc with "namespace": "foo.bar" conforms to this.

My query should only return documents where namespace is between "foo." and "foo.c". Again, my document conforms to this. This query, I believe, can never return a document that does not conform to the regex string in my Firestore rule.

As such, I'd expect to get a result set with my document.

What actually happens

index.cjs.js:13448 Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions. at new n (index.cjs.js:129) at index.cjs.js:10175 at index.cjs.js:10176 at n.onMessage (index.cjs.js:10209) at index.cjs.js:10115 at index.cjs.js:10146 at index.cjs.js:5542

I've tried modifying my query to not use a range, and to only have where("namespace", "==" "foo.bar"), and this works as expected, so it seems like the rest of the system is working fine, but there is a mismatch between the rules and the filter clause.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
istrupin
  • 1,423
  • 16
  • 32

1 Answers1

1

The problem here is not that your code is trying to access data that the rules don't allow, but that the rules engine isn't smart enough to be able to prove that for all cases of a match without having to check the actual data.

An interesting alternative would be to perform the same check with >= and <= operators. I didn't have a chance to try that though, so let me know if that works (or doesn't work) for you.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Frank you're a gem, that seemed to do the trick! I don't quite have the fine grained control with the Firestore string API that I'd like (I want my `<` clause to be dynamic based off of the next sortable UTF8 character from the end of my `>=` string), but since everything I'm doing is "." delimited, I made my Firestore rule allow based on `namespace >= token.my_namespace +"." && namespace < token.my_namespace + "/"` since "/" is the next character after "." in UTF8. That seems workable to me in a proof of concept, so I'm going to go ahead and give it a try for real. – istrupin Jun 29 '21 at 16:01
  • 1
    Thanks for confirming! For such queries we typically use \uF7FF, which is the last known Unicode character. But I definitely prefer something more readable when specific, like `~` when you're only doing ASCII chars - or the domain specific `/` for you. – Frank van Puffelen Jun 29 '21 at 16:13