12
votes

In firestore I want a user to only access a document if the user is in the teamid mentioned in the document. Now I have a different collection called teams where I have users mapped as { user_id = true }. So I have the following in the Firestore rules

return get(/databases/$(database)/documents/teams/$(resource.data.teamid)).data.approvedMembers[request.auth.uid] == true;

Now this rule does not work and fails any request made to the database by the frontend. But when I replace $(resource.data.teamid) with my actual teamid value as follows,

return get(/databases/$(database)/documents/teams/234234jk2jk34j23).data.approvedMembers[request.auth.uid] == true;

... it works as expected.

Now my question is am I using resource in a wrong way or is resource not supported in get() or exists() queries in Firestore rules?

Edit Complete rules as follows

service cloud.firestore {
  match /databases/{database}/documents {

    function isTeamMember() {
        return get(/databases/$(database)/documents/teams/$(resource.data.teamid)).data.approvedMembers[request.auth.uid] == true;
        // return exists(/databases/$(database)/documents/teams/$(resource.data.teamid));
    }

    match /{document=**} {
        allow read, write: if isTeamMember();
    }
  }
}

If you notice the commented out rule, exists() does not work in this case either.

2
Are you performing a query against multiple docs in a collection, or fetching a single document?Todd Kerpelman
Hey @ToddKerpelman. Yes I am querying multiple documents.Anubhav
Can you show more of your rules, including the match and allow get statements where this applies?Sam Stern
@SamStern I have added complete rules in the questionAnubhav

2 Answers

2
votes

Thanks for reaching out on Twitter. Following our chat, here is the conclusion:

Querying documents using your security rule

You cannot use a security rule as a filter. To get this to work, you must also add .where('teamid', '==', yourTeamId)

You have confirmed that this works for you, but you don't always want to restrict on one teamid

Using custom claims

You can set up custom claims in your authentication tokens. Here is an example of how to set these and them use them in your rules

Setting custom claim

You will need to use the Admin SDK for this.

auth = firebase.auth();

const userId = 'exampleUserId';
const customClaims = {teams: {myTeam1: true, myTeam2: true}};

return auth.setCustomUserClaims(userId, customClaims)
  .then(() => {
    console.log('Custom claim created');
    return auth.getUser(userId);
  })
  .then(userRecord => {
    console.log(`name: ${userRecord.displayName}`);
    console.log(`emailVerified: ${userRecord.emailVerified}`);
    console.log(`email: ${userRecord.email}`);
    console.log(`emailVerified: ${userRecord.emailVerified}`);
    console.log(`phoneNumber: ${userRecord.phoneNumber}`);
    console.log(`photoURL: ${userRecord.photoURL}`);
    console.log(`disabled: ${userRecord.disabled}`);
    console.log(`customClaims: ${JSON.stringify(userRecord.customClaims)}`);
  })
  .catch(err => {
    console.error(err);
  });

Apply security rule

allow read, write: if request.auth.token.teams.$(resource.data.teamId) == true;

I'm still not 100% certain that this will not also require you to filter by the teamid. Please test it and feed back.

1
votes

You have this match:

match /{document=**} {
    allow read, write: if isTeamMember();
}

That will match any document in the database. That means when you call isTeamMember() it's not guaranteed that resource represents a team document. If you have any subcollections on teams and write to those then resource will be the subcollection document.