I'm writing some Firestore rules and I'm seeing an error message when I try to read the data from a chat in my chats collection. I've seen other posts about this error, but none of them match my situation and I can't seem to figure out what the problem is.
Here are my security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid} {
allow read: if userIsSignedIn();
allow write: if request.auth.uid == uid;
}
match /users/{uid}/notifications/{notificationId} {
allow read: if resource.data.recipientUid == request.auth.uid;
}
match /bands/{bandId} {
allow read: if userIsSignedIn();
allow delete: if request.auth.uid == resource.data.adminUid;
allow list, update: if request.auth.uid in resource.data.memberUids;
match /members/{memberId} {
allow read: if userIsSignedIn();
allow delete: if request.auth.uid == resource.data.uid;
}
match /links/{linkId} {
allow read: if userIsSignedIn();
}
}
match /shows/{showId} {
allow read: if userIsSignedIn();
match /backlineItems/{backlineItemId} {
allow read: if userIsSignedIn();
}
match /participants/{participantId} {
allow read: if userIsSignedIn();
}
}
match /chats/{chatId} {
allow read: if request.auth.uid in resource.data.participantUids;
allow write: if true;
}
match /chats/{chatId}/messages/{messagesId} {
allow read, write: if userIsSignedIn();
}
}
function userIsSignedIn() {
return request.auth != null;
}
}
Here is the Swift query that is triggering an error message (I know this is what's causing it, I narrowed it down with breakpoints):
func getChat(withShowId showId: String) async throws -> Chat? {
do {
let chat = try await db
.collection("chats")
.whereField("showId", isEqualTo: showId)
.getDocuments()
guard !chat.documents.isEmpty && chat.documents[0].exists && chat.documents.count == 1 else { return nil }
let fetchedChat = try chat.documents[0].data(as: Chat.self)
return fetchedChat
} catch Swift.DecodingError.keyNotFound {
throw FirebaseError.dataNotFound
} catch {
throw FirebaseError.connection(
message: "Failed to fetch show chat.",
systemError: error.localizedDescription
)
}
}
When I run this query I get this error message: Property participantUids is undefined on object. for 'list' @ L41.
I'm pretty stuck and I have no idea what could be going wrong here. This error occurs both in Firebase Emulator and in my actual database. Any ideas?
EDIT: I added some debug statements to the rule, it looks like this now: match /chats/{chatId}
: allow read: if debug(debug(request.auth.uid) in debug(resource.data).participantUids);
Here's what's printed to the firestore-debug.log file when the exception is thrown:
string_value: "qvJ5tmKpih3mFkUCet5CPREg3qjZ" // The correct uid of the logged in user
map_value {
fields {
key: "showId" // The correct parameter that the query above is using to find the chat
value {
string_value: "Kk1NYjptTJITr0pjfVbe" // The correct value of the showId property for the chat that is to be fetched
}
}
}
What stands out to me is that the only property printed in the log file is the showId
property. I'm thinking this is because my query is filtering results based on the showId
property. Does this mean that, because I'm querying based on the showId
property, my rule must also be centered around the showId
property? I don't think that's the case, but I'm pretty stumped.
EDIT 2: I figured it out. I think the root cause of this issue is that I wasn't understanding that "rules are not filters", as it states in the Firestore Rules docs. I was trying to query for chats based on a showId
property, but because my rules don't provide any read guidelines for the showId
property of a chat, they would just reject the request altogether. Instead, I need to query for chats based on the participantUids
property because the rules know what to do with that property, but they don't know what to do with the showId
property.
Here's another quote from the docs that cleared this up for me: "If a query could potentially return documents that the client does not have permission to read, the entire request fails." Because the rules don't know anything about showId
, it's possible that approving a query based on this property would return documents that "the client does not have permission to read." As a result, the request is denied.
Your client side query has to match your rule. So in your case, change your query to:
let chat = try await db
.collection("chats")
.whereField("showId", isEqualTo: showId)
.whereField("participantUids", arrayContains: {youruid})
.getDocuments()