Правило безопасности Firestore для ограничения чтения сообщений чата

#javascript #google-cloud-firestore #firebase-security

#javascript #google-облако-firestore #firebase-безопасность

Вопрос:

В моем приложении для чата 1-1, использующем Firestore, 2 участника будут читать / записывать документы в msgs вложенной коллекции, где каждый документ представляет собой одно сообщение.
Но это сбой с отказом в разрешении из-за правил безопасности.

Структура БД :

 chatRooms/
    ${roomId}/
        msgs/
            ${msgId}
  

Я хочу добавить правила безопасности, чтобы только соответствующие 2 участника могли читать / записывать во msgs вложенную коллекцию своего roomId документа

Я написал следующие правила безопасности.
Это позволяет участникам создавать новый документ (т. Е. Отправлять сообщения) в msgs вложенной коллекции.
Но пользователи не могут прочитать эту msgs вложенную коллекцию.

 match /chatRooms/{roomId} {
    allow read:   if request.auth.uid == resource.data.userA.id || 
                     request.auth.uid == resource.data.userB.id;
    allow create: if request.auth.uid == request.resource.data.userA.id || 
                     request.auth.uid == request.resource.data.userB.id;

    match /msgs/{msgId} {
        allow read:   if request.auth.uid == resource.data.sender._id ||
                         request.auth.uid == resource.data.partner._id;
        allow create: if request.auth.uid == request.resource.data.sender._id ||
                         request.auth.uid == request.resource.data.partner._id;
    }
}
  

Код для чтения :

     const roomId = `${userAId}_${userBId}`;
    const query = db.collection('chatRooms').doc(roomId).collection('msgs')
        .orderBy('createdAt', 'desc')
        .startAfter(lastVisible)
        .limit(10)

    query.get()
  

Если я изменю правило чтения, как показано ниже, чтение и запись будут работать полностью нормально.
Таким образом, мы можем указать, что проблема заключается только в правиле чтения msgs вложенной коллекции. (И не в его родительской коллекции / правилах doc)

     match /msgs/{msgId} {
        allow read:   if request.auth != null;
        allow create: if request.auth.uid == request.resource.data.sender._id ||
                         request.auth.uid == request.resource.data.partner._id;
    }
  

Обновить :

Фрэнк предложил 3 решения, и я попробовал два из них (1 и 3), но оба не сработали для меня.

Для решения 1 я добавил ids массив с идентификаторами участников.
Сообщение о записи (добавление нового документа) работало с тем же правилом безопасности.

введите описание изображения здесь

Операция чтения выглядит следующим образом :

введите описание изображения здесь

И это правило безопасности :

введите описание изображения здесь


ОБНОВЛЕНИЕ 2

Это сработало после исправления правила безопасности, о чем Фрэнк упомянул в комментарии в своем ответе. (поскольку этот комментарий находится намного ниже, я добавляю его и здесь)

   match /msgs/{msgId} {
    allow read:   if request.auth.uid in resource.data.ids;
    allow create: if request.auth.uid in request.resource.data.ids;
  }
  

ОБНОВЛЕНИЕ 3

Решение 1 требовало создания индекса (который я orderBy также использую), что стоит дополнительно. Итак, я также попробовал решение Фрэнка 3, но оно возвращает ошибку «отказано в разрешении».
Вероятно, запрос на чтение неверен и требует некоторого фильтра, но я не могу добавить фильтр для $roomId данных документа ( userA.id / userB.id ), как того требуют правила, при чтении из msgs коллекции.

Правило :

 match /chatRooms/{roomId=**} {
  allow read:   if request.auth.uid == resource.data.userA.id || 
                   request.auth.uid == resource.data.userB.id;
  allow create: if request.auth.uid == request.resource.data.userA.id || 
                   request.auth.uid == request.resource.data.userB.id;
  allow update: if request.auth.uid == resource.data.userA.id || 
                   request.auth.uid == resource.data.userB.id;
}
  

Запрос на чтение :

     const query = dbFirestore.collection('chatRooms').doc(roomId).collection('msgs')
        .orderBy('createdAt', 'desc')
        .limit(10)

    query.onSnapshot( ... )
  

Комментарии:

1. Пожалуйста, не публикуйте скриншоты вашего кода или другого текстового содержимого. Вместо этого опубликуйте фактический текст и используйте инструменты форматирования Stack Overflow для его разметки.

Ответ №1:

Правила безопасности не фильтруют данные. Вместо этого они просто проверяют, разрешена ли операция чтения.

Поэтому, если вы хотите разрешить пользователю читать только сообщения, частью которых они являются, вам придется выполнить запрос, который выбирает эти сообщения.

Например: вы можете выбрать все сообщения, отправителем которых является пользователь, с помощью:

 db.collection('chatRooms').doc(roomId).collection('msgs')
    where('sender._id', '==', firebase.auth().currentUser.uid)
  

И теперь правила безопасности проверят, разрешен ли этот запрос, и вернут соответствующие сообщения.


Затем проблема заключается в том, что вы не можете выполнить условие ИЛИ для полей sender._id и partner._id . Для этого есть несколько решений:

  1. Сохраните пользовательские идентификаторы участников для каждого сообщения в поле массива (скажем participants ), а затем используйте array-contains для запроса:

     db.collection('chatRooms').doc(roomId).collection('msgs')
     where('participants', 'array-contains', firebase.auth().currentUser.uid)
      

    А затем закрепите это в правилах с помощью чего-то вроде:

     request.auth.uid in resource.data.participants
      
  2. Смоделируйте безопасность для комнаты на /chatRooms/$roomId уровне с помощью поиска в msgs вложенной коллекции. Для этого требуется два дополнительных получения, но в остальном все намного проще

     match /msgs/{msgId} {
     allow read: if request.auth != null amp;amp; 
         (get(/databases/$(database)/documents/chatRooms/$(roomId)).data.userA.id == request.auth.uid
         || get(/databases/$(database)/documents/chatRooms/$(roomId)).data.userB.id == request.auth.uid);
      
  3. Пусть доступ /msgs будет унаследован из комнаты:

     match /chatRooms/{roomId=**} {
       allow read: if request.auth.uid == resource.data.userA.id || 
                  request.auth.uid == resource.data.userB.id;
      

Комментарии:

1. Спасибо @Frank, ваше объяснение очень полезно. Я думаю, что я пойду с первым подходом.

2. Извините, я хотел сначала протестировать решения, а затем отметить их решаемыми. 3-е решение не сработало для меня, поэтому я попробовал первое. Но это тоже не сработало 🙁 ——— Прочитанный оп: query = db.collection('chatRooms').doc(roomId).collection('msgs').where('ids','array-contains',firebase.auth().currentUser.uid) ———— правило: match /msgs/{msgId} { allow read: if request.auth.uid == resource.data.ids[0] || request.auth.uid == resource.data.ids[1]; } ——— хотя write работал с тем же правилом, и я подтвердил ids массив с идентификаторами участников в сгенерированном документе

3. Пожалуйста, обновите свой вопрос новым запросом, правилами и скриншотом документа. Слишком сложно прочитать это в комментариях.

4. Эти сравнения массивов не будут работать, так как запрос выполняется array-contains , в то время как правила выполняют две == проверки. Я бы ожидал request.auth.uid in resource.data.ids .

5. Хммм… Я действительно не уверен, работает ли это в конце концов. В основном: как правила будут проверять операцию чтения из передаваемой вами информации. Так что это может быть невозможно с подходом 3.