javascriptgoogle-cloud-firestorefirebase-security

Messages snapshot error: FirebaseError: Missing or insufficient permissions. Request when trying to make a messaging system for my website


Here's the full console:

logindex.js:81  Messages snapshot error: FirebaseError: Missing or insufficient permissions.
eval @ index.js:81
eval @ index.esm2017.js:17548
setTimeout
pu @ index.esm2017.js:17547
error @ index.esm2017.js:17541
onError @ index.esm2017.js:15942
__PRIVATE_eventManagerOnWatchError @ index.esm2017.js:15889
__PRIVATE_removeAndCleanupTarget @ index.esm2017.js:16847
eval @ index.esm2017.js:16757
Promise.then
__PRIVATE_syncEngineRejectListen @ index.esm2017.js:16757
__PRIVATE_handleTargetError @ index.esm2017.js:15212
__PRIVATE_onWatchStreamChange @ index.esm2017.js:15221
onNext @ index.esm2017.js:14770
eval @ index.esm2017.js:14713
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:18943
eval @ index.esm2017.js:18976
Promise.then
Yu @ index.esm2017.js:18976
enqueue @ index.esm2017.js:18943
enqueueAndForget @ index.esm2017.js:18921
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:14713
i_ @ index.esm2017.js:14135
eval @ index.esm2017.js:14326
eval @ index.esm2017.js:14275
ab @ webchannel_blob_es2018.js:39
F @ webchannel_blob_es2018.js:37
Z.ta @ webchannel_blob_es2018.js:98
Rb @ webchannel_blob_es2018.js:53
M.Y @ webchannel_blob_es2018.js:47
M.ca @ webchannel_blob_es2018.js:44
ab @ webchannel_blob_es2018.js:39
F @ webchannel_blob_es2018.js:37
Wc @ webchannel_blob_es2018.js:76
h.bb @ webchannel_blob_es2018.js:75
h.Ea @ webchannel_blob_es2018.js:75
Lc @ webchannel_blob_es2018.js:71
h.Pa @ webchannel_blob_es2018.js:69
Promise.then
Nc @ webchannel_blob_es2018.js:69
h.Pa @ webchannel_blob_es2018.js:69
Promise.then
Nc @ webchannel_blob_es2018.js:69
h.Sa @ webchannel_blob_es2018.js:69
Promise.then
h.send @ webchannel_blob_es2018.js:66
h.ea @ webchannel_blob_es2018.js:74
Jb @ webchannel_blob_es2018.js:44
fd @ webchannel_blob_es2018.js:90
h.Fa @ webchannel_blob_es2018.js:89
Da @ webchannel_blob_es2018.js:28
Promise.then
x @ webchannel_blob_es2018.js:28
ec @ webchannel_blob_es2018.js:88
Rb @ webchannel_blob_es2018.js:53
M.Y @ webchannel_blob_es2018.js:47
M.ca @ webchannel_blob_es2018.js:44
ab @ webchannel_blob_es2018.js:39
F @ webchannel_blob_es2018.js:37
Wc @ webchannel_blob_es2018.js:76
h.bb @ webchannel_blob_es2018.js:75
h.Ea @ webchannel_blob_es2018.js:75
Lc @ webchannel_blob_es2018.js:71
h.Pa @ webchannel_blob_es2018.js:69
Promise.then
Nc @ webchannel_blob_es2018.js:69
h.Sa @ webchannel_blob_es2018.js:69
Promise.then
h.send @ webchannel_blob_es2018.js:66
h.ea @ webchannel_blob_es2018.js:74
Jb @ webchannel_blob_es2018.js:43
Hb @ webchannel_blob_es2018.js:42
h.Ga @ webchannel_blob_es2018.js:86
Da @ webchannel_blob_es2018.js:28
Promise.then
x @ webchannel_blob_es2018.js:28
fc @ webchannel_blob_es2018.js:84
h.connect @ webchannel_blob_es2018.js:82
Y.m @ webchannel_blob_es2018.js:96
Go @ index.esm2017.js:14266
send @ index.esm2017.js:14123
F_ @ index.esm2017.js:14626
U_ @ index.esm2017.js:14801
__PRIVATE_sendWatchRequest @ index.esm2017.js:15145
eval @ index.esm2017.js:15186
__PRIVATE_onWatchStreamOpen @ index.esm2017.js:15185
eval @ index.esm2017.js:14709
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:18943
eval @ index.esm2017.js:18976
Promise.then
Yu @ index.esm2017.js:18976
enqueue @ index.esm2017.js:18943
enqueueAndForget @ index.esm2017.js:18921
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:14708
n_ @ index.esm2017.js:14129
eval @ index.esm2017.js:14335
setTimeout
a_ @ index.esm2017.js:14330
k_ @ index.esm2017.js:14754
B_ @ index.esm2017.js:14705
eval @ index.esm2017.js:14695
Promise.then
auth @ index.esm2017.js:14686
start @ index.esm2017.js:14591
__PRIVATE_startWatchStream @ index.esm2017.js:15161
__PRIVATE_remoteStoreListen @ index.esm2017.js:15122
__PRIVATE_allocateTargetAndMaybeListen @ index.esm2017.js:16541
await in __PRIVATE_allocateTargetAndMaybeListen
__PRIVATE_syncEngineListen @ index.esm2017.js:16526
__PRIVATE_eventManagerListen @ index.esm2017.js:15824
eval @ index.esm2017.js:21633
await in eval
eval @ index.esm2017.js:18943
eval @ index.esm2017.js:18976
Promise.then
Yu @ index.esm2017.js:18976
enqueue @ index.esm2017.js:18943
enqueueAndForget @ index.esm2017.js:18921
__PRIVATE_firestoreClientListen @ index.esm2017.js:21633
onSnapshot @ index.esm2017.js:21637
eval @ index.js:70
eval @ index-68039fd7.js:3084
Promise.then
registerStateListener @ index-68039fd7.js:3080
onAuthStateChanged @ index-68039fd7.js:2943
onAuthStateChanged @ index-68039fd7.js:6987
eval @ index.js:50
./src/inbox/index.js @ inbox.bundle.js:279
__webpack_require__ @ inbox.bundle.js:313
(anonymous) @ inbox.bundle.js:382
(anonymous) @ inbox.bundle.js:384
index.js:81  Messages snapshot error: FirebaseError: Missing or insufficient permissions.
eval @ index.js:81
eval @ index.esm2017.js:17548
setTimeout
pu @ index.esm2017.js:17547
error @ index.esm2017.js:17541
onError @ index.esm2017.js:15942
__PRIVATE_eventManagerOnWatchError @ index.esm2017.js:15889
__PRIVATE_removeAndCleanupTarget @ index.esm2017.js:16847
eval @ index.esm2017.js:16757
Promise.then
__PRIVATE_syncEngineRejectListen @ index.esm2017.js:16757
__PRIVATE_handleTargetError @ index.esm2017.js:15212
__PRIVATE_onWatchStreamChange @ index.esm2017.js:15221
onNext @ index.esm2017.js:14770
eval @ index.esm2017.js:14713
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:18943
eval @ index.esm2017.js:18976
Promise.then
Yu @ index.esm2017.js:18976
enqueue @ index.esm2017.js:18943
enqueueAndForget @ index.esm2017.js:18921
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:14713
i_ @ index.esm2017.js:14135
eval @ index.esm2017.js:14326
eval @ index.esm2017.js:14275
ab @ webchannel_blob_es2018.js:39
F @ webchannel_blob_es2018.js:37
Z.ta @ webchannel_blob_es2018.js:98
Rb @ webchannel_blob_es2018.js:53
M.Y @ webchannel_blob_es2018.js:47
M.ca @ webchannel_blob_es2018.js:44
ab @ webchannel_blob_es2018.js:39
F @ webchannel_blob_es2018.js:37
Wc @ webchannel_blob_es2018.js:76
h.bb @ webchannel_blob_es2018.js:75
h.Ea @ webchannel_blob_es2018.js:75
Lc @ webchannel_blob_es2018.js:71
h.Pa @ webchannel_blob_es2018.js:69
Promise.then
Nc @ webchannel_blob_es2018.js:69
h.Sa @ webchannel_blob_es2018.js:69
Promise.then
h.send @ webchannel_blob_es2018.js:66
h.ea @ webchannel_blob_es2018.js:74
Jb @ webchannel_blob_es2018.js:44
fd @ webchannel_blob_es2018.js:90
h.ab @ webchannel_blob_es2018.js:89
eval @ webchannel_blob_es2018.js:40
setTimeout
ub @ webchannel_blob_es2018.js:40
h.Fa @ webchannel_blob_es2018.js:89
Da @ webchannel_blob_es2018.js:28
Promise.then
x @ webchannel_blob_es2018.js:28
ec @ webchannel_blob_es2018.js:88
Rb @ webchannel_blob_es2018.js:53
M.Y @ webchannel_blob_es2018.js:47
M.ca @ webchannel_blob_es2018.js:44
ab @ webchannel_blob_es2018.js:39
F @ webchannel_blob_es2018.js:37
Wc @ webchannel_blob_es2018.js:76
h.bb @ webchannel_blob_es2018.js:75
h.Ea @ webchannel_blob_es2018.js:75
Lc @ webchannel_blob_es2018.js:71
h.Pa @ webchannel_blob_es2018.js:69
Promise.then
Nc @ webchannel_blob_es2018.js:69
h.Sa @ webchannel_blob_es2018.js:69
Promise.then
h.send @ webchannel_blob_es2018.js:66
h.ea @ webchannel_blob_es2018.js:74
Jb @ webchannel_blob_es2018.js:43
Hb @ webchannel_blob_es2018.js:42
h.Ga @ webchannel_blob_es2018.js:86
Da @ webchannel_blob_es2018.js:28
Promise.then
x @ webchannel_blob_es2018.js:28
fc @ webchannel_blob_es2018.js:84
h.connect @ webchannel_blob_es2018.js:82
Y.m @ webchannel_blob_es2018.js:96
Go @ index.esm2017.js:14266
send @ index.esm2017.js:14123
F_ @ index.esm2017.js:14626
U_ @ index.esm2017.js:14801
__PRIVATE_sendWatchRequest @ index.esm2017.js:15145
eval @ index.esm2017.js:15186
__PRIVATE_onWatchStreamOpen @ index.esm2017.js:15185
eval @ index.esm2017.js:14709
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:18943
eval @ index.esm2017.js:18976
Promise.then
Yu @ index.esm2017.js:18976
enqueue @ index.esm2017.js:18943
enqueueAndForget @ index.esm2017.js:18921
eval @ index.esm2017.js:14736
eval @ index.esm2017.js:14708
n_ @ index.esm2017.js:14129
eval @ index.esm2017.js:14335
setTimeout
a_ @ index.esm2017.js:14330
k_ @ index.esm2017.js:14754
B_ @ index.esm2017.js:14705
eval @ index.esm2017.js:14695
Promise.then
auth @ index.esm2017.js:14686
start @ index.esm2017.js:14591
__PRIVATE_startWatchStream @ index.esm2017.js:15161
__PRIVATE_remoteStoreListen @ index.esm2017.js:15122
__PRIVATE_allocateTargetAndMaybeListen @ index.esm2017.js:16541
await in __PRIVATE_allocateTargetAndMaybeListen
__PRIVATE_syncEngineListen @ index.esm2017.js:16526
__PRIVATE_eventManagerListen @ index.esm2017.js:15824
eval @ index.esm2017.js:21633
await in eval
eval @ index.esm2017.js:18943
eval @ index.esm2017.js:18976
Promise.then
Yu @ index.esm2017.js:18976
enqueue @ index.esm2017.js:18943
enqueueAndForget @ index.esm2017.js:18921
__PRIVATE_firestoreClientListen @ index.esm2017.js:21633
onSnapshot @ index.esm2017.js:21637
eval @ index.js:70
eval @ index-68039fd7.js:3084
Promise.then
registerStateListener @ index-68039fd7.js:3080
onAuthStateChanged @ index-68039fd7.js:2943
onAuthStateChanged @ index-68039fd7.js:6987
eval @ index.js:50
./src/inbox/index.js @ VM6102 inbox.bundle.js:279
__webpack_require__ @ VM6102 inbox.bundle.js:313
(anonymous) @ VM6102 inbox.bundle.js:382
(anonymous) @ VM6102 inbox.bundle.js:384

I don't know what's causing it, my other Firestore services work, like writing and reading the files of one's own file (I have all users on their own file with their own UID). Here's my firebase security rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // ——— messages collection ———
    match /messages/{messageId} {

      // Allow read only if authenticated user is in the participants array
      allow read: if request.auth != null &&
                  request.auth.uid in resource.data.participants;

      // Allow create only if authenticated user is in the participants array being created
      allow create: if request.auth != null &&
                    request.auth.uid in request.resource.data.participants;

      // Disallow updates and deletes
      allow update, delete: if false;
    }

    // ——— users collection ———
    match /users/{userId} {
      // Anyone authenticated can read any user document
      allow read: if request.auth != null;

      // Only allow a user to update their own document
      allow update: if request.auth != null && request.auth.uid == userId;
    }

  }
}

My code works if I make my rules public (like everyone can write and read), however the moment I try to put rules, my messaging system doesn't work.

My query looks like this:

const chatQ = query(
  collection(db, 'messages'),
  where('participants', '==', participants),
  orderBy('timestamp', 'asc')
);

My current js file for my messaging page is here:

import '../css/styles.css';
import { btnLogout } from './ui.js';

import { initializeApp } from 'firebase/app';
import {
  getAuth,
  signOut,
  onAuthStateChanged,
} from 'firebase/auth';
import {
  getFirestore,
  collection,
  query,
  where,
  orderBy,
  onSnapshot,
  addDoc,
  Timestamp
} from 'firebase/firestore';

// ——— Firebase init ———
const firebaseApp = initializeApp({
  apiKey:    "AIzaSyA_SIYh8CCbC12BmFOYS1VBSJLVnCBNu0c",
  authDomain:"citysurfer-609ab.firebaseapp.com",
  projectId: "citysurfer-609ab",
  storageBucket: "citysurfer-609ab.appspot.com",
  messagingSenderId: "736165172289",
  appId:     "1:736165172289:web:0f75f82abf121cdb06e2c0",
  measurementId: "G-7LHT92W2NX"
});
const auth = getAuth(firebaseApp);
const db   = getFirestore(firebaseApp);

// ——— UI refs ———
const msgForm     = document.getElementById('messageForm');
const msgInput    = document.getElementById('messageInput');
const msgList     = document.getElementById('messageList');
const params      = new URLSearchParams(window.location.search);
const recipientId = params.get('uid');

let chatInitialized = false;

// ——— Logout ———
btnLogout?.addEventListener('click', async () => {
  await signOut(auth);
  window.location.replace('login.html');
});

// ——— Main listener ———
onAuthStateChanged(auth, user => {
  if (!user) {
    return window.location.replace('login.html');
  }
  if (!recipientId) {
    return console.error('Missing recipient UID in URL');
  }
  if (chatInitialized) return;
  chatInitialized = true;

  // Create sorted participants array for consistent ordering
  const participants = [user.uid, recipientId].sort();

  // Query messages where participants match (array equality)
  const chatQ = query(
    collection(db, 'messages'),
    where('participants', '==', participants),
    orderBy('timestamp', 'asc')
  );

  onSnapshot(chatQ,
    snap => {
      msgList.innerHTML = '';
      snap.forEach(docSnap => {
        const m = docSnap.data();
        const li = document.createElement('li');
        li.textContent = `${m.from === user.uid ? 'You' : 'Them'}: ${m.text}`;
        msgList.appendChild(li);
      });
    },
    err => {
      console.error('Messages snapshot error:', err);
      msgList.innerHTML = `<li class="text-red-600">Error loading chat.</li>`;
    }
  );

  // — Send new messages — 
  msgForm?.addEventListener('submit', async e => {
    e.preventDefault();
    const text = msgInput.value.trim();
    if (!text) return;

    try {
      await addDoc(collection(db, 'messages'), {
        from:         user.uid,
        to:           recipientId,
        participants: participants,  // <--- Added participants array here
        text,
        timestamp:    Timestamp.now()
      });
      msgInput.value = '';
    } catch (writeErr) {
      console.error('Send message error:', writeErr);
      alert('Failed to send message.');
    }
  });
});

Solution

  • Just an update, I was able to fix it, not quite sure how it worked, but it was an issue with the query and not the rules. However I did slightly change my rules. Here is my query itself:

    const chatQ = query(
      collection(db, 'messages'),
      where('participants', 'array-contains', me),
      orderBy('timestamp', 'asc')
    );
    

    If anyone wants to see more of my code and what I did please let me know but I'm just posting this answer to mark this as done haha thank you!