Reputation: 63
I've got a top-level collection of documents, each of which has an array field containing the email addresses of the users that are permitted to view the document in question (there's a reason in this case that it's the email addresses, not the UIDs). Additionally, I have a custom claim set up on some users that mark them as admin who should have read/write access to everything.
The following security rule works well for the top level:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /accounts/{account} {
function allowRead() {
return resource.data.users.hasAny([request.auth.token.email]);
}
allow read: if allowRead();
allow read, write: if request.auth.token.admin == true;
}
}
}
I want the rule to check the 'users' array field when requesting documents in a collectionGroup query across nested collections called 'projects' and block access if a nested document is requested for which there is no match to the 'users' array field in the parent.
I get that the allowRead() funtion won't work as is, even if I add a recursive wildcard after match /accounts/{account}
like /accounts/{account}/{document=**}
since the definition of resource.data
will be different at that level and won't return true for the allowRead() function.
I know that Firestore rules don't really support an operation like 'check foo in parent' when querying, so I was hoping for some advice on the best way to approach this requirement.
The collectionGroup query to the nested collections is
this.firestore.collectionGroup('projects', (ref) => ref.where('accountId','in',accountIds)).get()
in which 'accountIds' is a given array of the ids of the parent account, which is stored in each document in the projects collection for ease of querying with collectionGroup.
I don't really want to move the nested collection to top level because then I'd have to store the users array in each document as well as the reference to the account to which it belongs, but perhaps that's the best way to do this?
Or am I thinking about this whole thing wrong? I'm fairly new to Firebase.
Upvotes: 1
Views: 446
Reputation: 63
I've solved this with the following:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /accounts/{account} {
function allowRead() {
return resource.data.users.hasAny([request.auth.token.email]);
}
allow read,write: if request.auth.token.admin == true;
allow read: if allowRead();
}
match /{path=**}/projects/{project} {
allow read,write: if request.auth.token.admin == true;
allow read: if get(/databases/$(database)/documents/accounts/$(resource.data.accountId)).data.users.hasAny([request.auth.token.email]);
}
}
}
The only issue is that as far as I can tell, this is going to double the amount of billable queries because there's an extra 'get' made any time the nested collection is queried.
Any thoughts from the community on better ways to do this would be welcome!
Upvotes: 1