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.
Thanks for reaching out on Twitter. Following our chat, here is the conclusion:
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
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
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);
});
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With