13

Can you make security rules that runs a query to check if a matching document is found?

I'm building a system where a logged in user can vote on specific topics.

Every single vote will be saved in its own document, with a reference to the user, the topic etc.

I want to make a security rule that checks if there's already a document in the vote section with that specific user ID and topic ID present, and only let the user write a vote document it that's not the case.

I can't see any query options in the documentation, so what are my options?

Can I somehow create an index of all votes, and look for a specific document path in that index?

Or should I give the votes a custom ID scheme, based on the user ID and the topic ID, so they can be found?

Esben von Buchwald
  • 2,772
  • 1
  • 29
  • 37

2 Answers2

13

You can't perform a query with conditions with security rules. All you can do is get() a document using its known path. That is to say, you must know all of its collections and document IDs that uniquely identify it.

It sounds like it might be feasible to create others documents (they don't have to contain any data), and their existence can signal the condition you want to protect.

(Yes, sometimes modeling of data has to take security rules into consideration.)

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • I just had another idea. Since I need to disable voting for all topics where the user has already voted, I was thinking of having a document for each user with a list of all its votes topics. Maybe I could just load the document from and then check if that document contains the topic ID already? something like `write if get(/databases/$(database)/documents/users/$(request.auth.uid)/attributes/votes).data[request.resource.data.topic]` – Esben von Buchwald May 14 '19 at 19:56
  • 1
    Sure, you can check for a value in an array field in a document. – Doug Stevenson May 14 '19 at 19:57
  • Great. I guess a numeric array is not the optimal choice here, and an object with the topic IDs as keys, and the value of `true` would be easier to look up? – Esben von Buchwald May 14 '19 at 20:00
  • Whichever works for your case. You can use either in security rules. – Doug Stevenson May 14 '19 at 20:04
  • Do you have an example of finding an element in an array in a document at a specific path, during a security rule evaluation? – Esben von Buchwald May 15 '19 at 07:41
  • 1
    You can use `hasAny` on the list type document field to determine if it contains some other item. https://firebase.google.com/docs/reference/rules/rules.List#hasAny – Doug Stevenson May 16 '19 at 22:02
0

For those still interested, this has now changed.

Please see https://firebase.blog/posts/2022/09/announcing-cross-service-security-rules/

You can now use the functions firestore.get() and firestore.exists() to query the database inside Security Rules.

These functions, will however, incur additional reads even if the request is denied.

obevan
  • 104
  • 7
  • While this is helpful to know, it doesn't answer the question. The question was about making a query with a filter in security rules (potentially getting 0 or more documents), not crossing between Firebase products. It's still not possible to make a filtered query in security rules. You can only get a document by ID. – Doug Stevenson Aug 28 '23 at 19:56
  • You can see other questions on Stack Overflow that *DO* ask the question of crossing between services, and have properly updated answers [here](https://stackoverflow.com/questions/49053399/is-it-possible-to-set-storage-rules-using-firestore-of-firebase) and [here](https://stackoverflow.com/questions/56877903/firebase-storage-rules-that-query-data-from-firestore). – Doug Stevenson Aug 28 '23 at 20:06