Ben Singer
Ben Singer

Reputation: 51

Firestore rules: How do I check the contents of a map on all documents in a collection?

I am trying to accomplish the following functionality:

I'm struggling on that last one. I tried this:

!exists(/databases/$(database)/documents/matches/$(uid)/players/$(request.auth.uid)) // if the user is not already in another match

...but it doesn't seem to work.

Here are my rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /userAuthData/{uid}/{documents=**} {
      allow read: if false
      allow write: if false
    }
    match /userData/{uid}/{documents=**} {
      allow read: if request.auth != null && request.auth.uid == uid
      allow write: if false
    }

    function allowCreateMatch(uid) {
      return request.auth != null 
        && exists(/databases/$(database)/documents/userData/$(request.auth.uid)) // if the user exists
        && !exists(/databases/$(database)/documents/matches/$(request.auth.uid)) // if the user does not have their own match already
        && !exists(/databases/$(database)/documents/matches/$(uid)/players/$(request.auth.uid)) // if the user is not already in another match
        && request.resource.data.maxPlayers == 2 // if the match is set to have 2 players (1v1 is the default for now)
        && request.resource.data.state == 1 // if the match is set to a state of WAITING_FOR_PLAYERS
    }

    function allowJoinMatch(uid) {
      return request.auth != null 
        && exists(/databases/$(database)/documents/userData/$(request.auth.uid)) // if the user exists
        && !exists(/databases/$(database)/documents/matches/$(request.auth.uid)) // if the user does not have their own match already
        && !exists(/databases/$(database)/documents/matches/$(uid)/players/$(request.auth.uid)) // if the user is not already in another match
        && !(request.auth.uid in resource.data.players) // if the user is not already in the match
        && resource.data.players.keys().toSet().size() < resource.data.maxPlayers // if there is room left based on the max number of players
        && resource.data.state == 1 // if the match is in a state of WAITING_FOR_PLAYERS
        && resource.data.maxPlayers == request.resource.data.maxPlayers // if there max players value is the same
        && request.resource.data.players.keys().toSet().size() + 1 == resource.data.maxPlayers
          ? request.resource.data.state == 2 // the match is should be set to a state of READY_TO_START
          : request.resource.data.state == 1 // the match is should be set to a state of WAITING_FOR_PLAYERS
    }

    function allowDeletion() {
      return request.auth.uid == resource.id
        && resource.data.state == 1
    }

    match /matches/{uid}/{documents=**} {
      allow read: if true
      allow create: if allowCreateMatch(uid)
      allow update: if allowJoinMatch(uid)
      allow delete: if allowDeletion()
    }
  }
}

Upvotes: 0

Views: 90

Answers (1)

Doug Stevenson
Doug Stevenson

Reputation: 317427

It's not possible with security rules to perform a query other than a simple single-document get(). Queries for unknown numbers of documents are not allowed because they would not scale in the way that security rules require.

If you can't express your rule in terms of getting a single document, then you will either need to change your data in some way to make this possible, or route your write through some backend that can perform the query you need.

Upvotes: 1

Related Questions