Update your firestore.rules file with the following:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// User's profile document
match /users/{userId}/profile/info {
// Users can read and write their own profile
allow read, write: if request.auth != null && request.auth.uid == userId;
// Allow reading any profile when searching by email (for sharing feature)
// This is needed for the collectionGroup query when adding users to share with
allow read: if request.auth != null;
}
// Allow collectionGroup query on all profile subcollections
match /{path=**}/profile/{document} {
allow read: if request.auth != null;
}
// User's customer cards
match /users/{userId}/customer_cards/{cardId} {
// Users can read their own cards
// OR cards from users who have shared with them (checked via sharing_with_me collection)
allow read: if request.auth != null &&
(request.auth.uid == userId ||
exists(/databases/$(database)/documents/users/$(request.auth.uid)/sharing_with_me/$(userId)));
// Users can only write (create, update, delete) their own cards
allow write: if request.auth != null && request.auth.uid == userId;
}
// User's shared_with collection (who they share their cards with)
match /users/{userId}/shared_with/{sharedUserId} {
// Users can read and manage their own sharing list
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// User's sharing_with_me collection (who is sharing cards with them)
match /users/{userId}/sharing_with_me/{sharerUserId} {
// Users can read their own sharing_with_me list
allow read: if request.auth != null && request.auth.uid == userId;
// Only the sharer can write to this collection (when they add/remove you)
allow write: if request.auth != null && request.auth.uid == sharerUserId;
}
}
}- Users can read and write their own profile
- Any authenticated user can read profiles (needed for the sharing feature to find users by email)
- This is safe because profiles only contain email and username
- Users can read cards if:
- They own the cards, OR
- The card owner's UID exists in the current user's
sharing_with_mecollection
- Users can only write (create/update/delete) their own cards
- Users can only read and write their own
shared_withlist - Tracks who you are sharing your cards with
- Users can read their own
sharing_with_melist - Only the sharing user can write to this collection
- This prevents manual manipulation and ensures bidirectional sync
-
Adding a share:
- User A enters User B's email
- App queries profiles to find User B's UID
- Creates two documents:
users/{userA_uid}/shared_with/{userB_uid}- "I'm sharing with B"users/{userB_uid}/sharing_with_me/{userA_uid}- "A is sharing with me"
- Both documents contain:
{ email, userId, addedAt }
-
User B viewing shared cards:
- App fetches User B's own cards
- App reads
users/{userB_uid}/sharing_with_me/collection (fast!) - For each entry, fetches that user's cards
- Security rule allows this because
sharing_with_me/{userA_uid}exists
-
Removing a share:
- User A clicks remove
- Deletes both documents:
users/{userA_uid}/shared_with/{userB_uid}users/{userB_uid}/sharing_with_me/{userA_uid}
users/
{userA_uid}/
profile/
info: { email, username?, createdAt, updatedAt }
customer_cards/
{cardId}: { store_name, code, barcode_type, shop_locations }
shared_with/
{userB_uid}: { email, userId, addedAt }
{userB_uid}/
profile/
info: { email, username?, createdAt, updatedAt }
customer_cards/
{cardId}: { store_name, code, barcode_type, shop_locations }
sharing_with_me/
{userA_uid}: { email, userId, addedAt }
// Had to fetch ALL users to check sharing
const usersSnapshot = await getDocs(collection(db, 'users')); // 😱
for (const userDoc of usersSnapshot.docs) {
// Check each user's shared_with collection...
}Problem: If you have 10,000 users, you'd fetch 10,000 documents even if only 2 people share with you!
// Only fetch users who are sharing with you
const sharingSnapshot = await getDocs(
collection(db, `users/${currentUserId}/sharing_with_me`)
); // 🚀Benefit: If 2 people share with you, you only fetch 2 documents + their cards!
- Profile visibility: Profiles are readable by all authenticated users for the sharing feature
- Card privacy: Cards are only visible to the owner and explicitly shared users
- One-way sharing: Sharing is not mutual - if A shares with B, B does NOT automatically share with A
- Tamper-proof: Users cannot manually add entries to their
sharing_with_mecollection (security rule prevents this)
The app maintains bidirectional consistency:
- When adding: Both
shared_withandsharing_with_meare created - When removing: Both documents are deleted
- If one operation fails, the other might succeed (eventual consistency trade-off)
For production, consider using Firebase Cloud Functions to ensure atomic operations.
You can test these rules in the Firebase Console:
- Go to Firestore Database → Rules
- Click "Rules Playground"
- Test scenarios:
- User A reading their own cards ✓
- User B reading User A's cards (after A shares with B) ✓
- User C reading User A's cards (without sharing) ✗
- User B writing to User A's cards ✗
- User B manually adding to their own
sharing_with_me✗ - User A adding to User B's
sharing_with_me✓