Closed
Description
[REQUIRED] Environment info
Firebase CLI 13.31.2
Running on Flutter web in Chrome
cloud_firestore: ^5.6.6
Framework • revision c236373904 (3 weeks ago) • 2025-03-13 16:17:06 -0400
Engine • revision 18b71d647a
Tools • Dart 3.7.2 • DevTools 2.42.3
[REQUIRED] Test case
To reproduce, run the following flutter app main...
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
void main() async {
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: "your-cred",
projectId: "your-cred",
storageBucket: "your-cred",
messagingSenderId: "your-cred",
appId: "your-cred",
),
);
FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
final querySnapshot = await (FirebaseFirestore.instance.collection('invites').where('recipientUid', isEqualTo: "anything")).get();
}
With the following rules...
service cloud.firestore {
match /databases/{database}/documents {
// Default deny all
match /{document=**} {
allow read, write: if false;
}
// Check if user is authenticated
function isAuthenticated() {
return request.auth != null;
}
// Check if user is accessing their own data
function isOwner(userId) {
return request.auth.uid == userId;
}
// Public user data collection
match /publicUserData/{userId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isOwner(userId);
allow update, delete: if isAuthenticated() && isOwner(userId);
}
// Sports leagues
match /sportsLeagues/{leagueId} {
allow read: if isAuthenticated();
allow write: if isAuthenticated() && isAdmin();
match /teams/{teamId} {
allow read: if isAuthenticated();
allow write: if isAuthenticated() && isAdmin();
}
}
// Memberships collection
match /memberships/{membershipId} {
allow read: if isAuthenticated();
allow create, update: if isAuthenticated() && request.resource.data.uid == request.auth.uid;
allow delete: if false;
}
// Invites collection
match /invites/{inviteId} {
allow read, delete: if isAuthenticated() &&
debug(/databases/$(database)/documents/memberships/$(inviteId));
allow create: if isAuthenticated() && isPickGroupAdmin(buildMembershipIdFromInvite(request));
allow update: if false;
}
function isInviteRecipient(request) {
return request.auth.uid == request.resource.data.recipientUid;
}
function buildMembershipIdFromInvite(request) {
return request.resource.data.senderUid+'-'+request.resource.data.groupId;
}
// Pick groups collection
match /pickGroups/{groupId} {
allow read: if isAuthenticated(); // this cannot be made more strict as long as PickGroupService checks if name is unique
allow create: if isAuthenticated();
allow update: if isAuthenticated() && isPickGroupAdmin(request.auth.uid+'-'+request.resource.data.groupId);
allow delete: if false;
}
function isPickGroupAdmin(id) {
return exists(/databases/$(database)/documents/memberships/$(id)) &&
get(/databases/$(database)/documents/memberships/$(id)).data.role == 'admin';
}
function isAdmin() {
return exists(/databases/$(database)/documents/admins/$(request.auth.uid));
}
}
}
In this case the expected behaviour is that the path object would be written out in the firestore-debug.log.