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