firestore rules update needed
groups need write access to firestore. go to your firebase console
-> firestore -> rules, and replace your rules with the following:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users - own data only
match /users/{uid} {
allow read: if request.auth != null;
allow write: if request.auth != null && request.auth.uid == uid;
}
// Usernames - anyone authenticated can read; write only to claim your own
match /usernames/{username} {
allow read: if request.auth != null;
allow create: if request.auth != null
&& request.resource.data.uid == request.auth.uid;
allow delete: if request.auth != null
&& resource.data.uid == request.auth.uid;
}
// Friendships
match /friendships/{fsId} {
allow read, write: if request.auth != null
&& request.auth.uid in resource.data.users;
allow create: if request.auth != null
&& request.auth.uid in request.resource.data.users;
}
// Friend requests
match /friendRequests/{reqId} {
allow read: if request.auth != null
&& (resource.data.from == request.auth.uid
|| resource.data.to == request.auth.uid);
allow create: if request.auth != null
&& request.resource.data.from == request.auth.uid;
allow update: if request.auth != null
&& (resource.data.from == request.auth.uid
|| resource.data.to == request.auth.uid);
}
// Feed posts
match /feed/{postId} {
allow read: if (resource.data.visibility == 'public')
|| request.auth != null;
allow create: if request.auth != null
&& request.resource.data.uid == request.auth.uid;
allow update: if request.auth != null;
allow delete: if request.auth != null
&& resource.data.uid == request.auth.uid;
// Comments
match /comments/{commentId} {
allow read: if get(/databases/$(database)/documents/feed/$(postId)).data.visibility == 'public'
|| request.auth != null;
allow create: if request.auth != null;
allow delete: if request.auth != null
&& resource.data.uid == request.auth.uid;
}
}
// Messages
match /messages/{msgId} {
allow read: if request.auth != null
&& (resource.data.from == request.auth.uid
|| resource.data.to == request.auth.uid);
allow create: if request.auth != null
&& request.resource.data.from == request.auth.uid;
allow update: if request.auth != null
&& (resource.data.from == request.auth.uid
|| resource.data.to == request.auth.uid);
}
// Around camera posts - visible to signed-in users, author controls their own post
match /aroundPosts/{postId} {
allow read: if request.auth != null;
allow create: if request.auth != null
&& request.resource.data.uid == request.auth.uid;
allow update, delete: if request.auth != null
&& resource.data.uid == request.auth.uid;
}
// Message requests for non-friend DMs
match /messageRequests/{uid}/items/{fromUid} {
allow read, write: if request.auth != null
&& (request.auth.uid == uid || request.auth.uid == fromUid);
}
// Notifications
match /notifications/{uid}/items/{notifId} {
allow read, write: if request.auth != null
&& request.auth.uid == uid;
}
// Groups - members can read; only owners can manage settings, members, and channels
match /groups/{groupId} {
allow read: if request.auth != null
&& request.auth.uid in resource.data.members;
allow create: if request.auth != null
&& request.auth.uid in request.resource.data.members;
allow update: if request.auth != null
&& (
resource.data.ownerId == request.auth.uid
|| (
!(request.auth.uid in resource.data.members)
&& exists(/databases/$(database)/documents/groups/$(groupId)/invites/$(request.auth.uid))
&& get(/databases/$(database)/documents/groups/$(groupId)/invites/$(request.auth.uid)).data.status == 'pending'
&& request.auth.uid in request.resource.data.members
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['members','memberNames','memberPhotos'])
)
|| (
request.auth.uid in resource.data.members
&& !(request.auth.uid in request.resource.data.members)
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['members','memberNames','memberPhotos'])
)
);
allow delete: if request.auth != null
&& resource.data.ownerId == request.auth.uid;
// Group invites
match /invites/{uid} {
allow read: if request.auth != null
&& (request.auth.uid == uid
|| request.auth.uid in get(/databases/$(database)/documents/groups/$(groupId)).data.members);
allow create, update, delete: if request.auth != null
&& (
get(/databases/$(database)/documents/groups/$(groupId)).data.ownerId == request.auth.uid
|| request.auth.uid == uid
);
}
// Channels inside a group
match /channels/{channelId} {
allow read: if request.auth != null
&& request.auth.uid in get(/databases/$(database)/documents/groups/$(groupId)).data.members;
allow create, update, delete: if request.auth != null
&& get(/databases/$(database)/documents/groups/$(groupId)).data.ownerId == request.auth.uid;
// Messages inside a channel
match /messages/{msgId} {
allow read, write: if request.auth != null
&& request.auth.uid in get(/databases/$(database)/documents/groups/$(groupId)).data.members;
}
}
// Voice presence inside a group
match /voice_presence/{uid} {
allow read: if request.auth != null
&& request.auth.uid in get(/databases/$(database)/documents/groups/$(groupId)).data.members;
allow write: if request.auth != null
&& request.auth.uid == uid
&& request.auth.uid in get(/databases/$(database)/documents/groups/$(groupId)).data.members;
allow delete: if request.auth != null
&& (
request.auth.uid == uid
|| get(/databases/$(database)/documents/groups/$(groupId)).data.ownerId == request.auth.uid
);
}
}
// Presence / lastSeen inside users doc (already covered above)
}
}