Firestoreでチャットルームを作る時のSecurity Rulesを考えてみた。

# [Firestore](https://firebase.google.com/docs/firestore/)でチャットルームを作る時のSecurity Rulesを考えてみた。

Firebaseの新しいサービス[Firestore](https://firebase.google.com/docs/firestore/)を使って、チャットルームを作成する時のSecurity Rulesを検討してみました。

前提として、以下のようなチャットルームを想定。

* １対１
* 書き込みは削除できない
* 未読/既読の状態を持つ


Security Rulesのベースは、[Firestore](https://firebase.google.com/docs/firestore/)の[マニュアル](https://firebase.google.com/docs/firestore/security/get-started#edit_firestore_rules)に参考になる記載があり、今回は、それを発展させて検討しました。

## 検討結果のSecurity Rules.

```
service cloud.firestore {
  match /databases/{database}/documents {
    allow read, write: if false;
    match /users/{userId} {
      allow read, write: if request.auth.uid == userId;
      match /rooms/{roomId} {
        allow read, write: if request.auth.uid == userId;
      }
    }
    match /rooms/{roomId} {
      allow read, write: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/rooms/$(roomId));
      match /messages/{messageId} {
        allow read: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/rooms/$(roomId));
        allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/rooms/$(roomId))
                             && request.resource.data.keys().hasAll(["datetime", "content", "userId"])
                             && request.resource.data.size() == 3
                             && request.resource.data.datetime is timestamp
                             && request.time < request.resource.data.datetime + duration.value(1, 'm')
                             && request.resource.data.content is string
                             && request.resource.data.userId is string;
        allow update: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/rooms/$(roomId))
                             && request.resource.data.keys().hasAll(["read"])
                             && request.resource.data.read is bool
                             && resource.data.datetime == request.resource.data.datetime
                             && resource.data.content == request.resource.data.content
                             && resource.data.userId == request.resource.data.userId
                             && request.resource.data.size() == 4;
      }
    }
  }
}
```

ユーザ毎に自身のチャットルーム(`rooms`)のID(`roomId`)を持ち、チャットルーム毎にメッセージのcollectionを持つ構成は、[マニュアル](https://firebase.google.com/docs/firestore/security/get-started#edit_firestore_rules)に記載されている通り。
追加のRuleとして、メッセージとして格納する情報を厳密に評価することで、セキュリティを確保しています。

## メッセージ追加時のルール

```
        allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/rooms/$(roomId))
                             && request.resource.data.keys().hasAll(["datetime", "content", "userId"])
                             && request.resource.data.size() == 3
                             && request.resource.data.datetime is timestamp
                             && request.time < request.resource.data.datetime + duration.value(1, 'm')
                             && request.resource.data.content is string
                             && request.resource.data.userId is string;
```

### メッセージ追加(`allow create`)で設定している評価

* 追加するフィールドを明確にして`key`名称の評価
* 追加するデータの`size`の評価
* データの`value`の型の評価
* 追加されるメッセージの時間評価
作成されるデータに時間の不正が行われないよう現在日時から1分以内の追加であるかどうかを評価(`request.time < request.resource.data.datetime + duration.value(1, 'm')`)


## メッセージ既読処理時のルール

```
        allow update: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/rooms/$(roomId))
                             && request.resource.data.keys().hasAll(["read"])
                             && request.resource.data.read is bool
                             && resource.data.datetime == request.resource.data.datetime
                             && resource.data.content == request.resource.data.content
                             && resource.data.userId == request.resource.data.userId
                             && request.resource.data.size() == 4;
```

### メッセージ既読処理時(`allow update`)で設定している評価

既読処理として、未読/既読を制御するフラグ(`read`)しか書き換えられないようにします。

* updateされる情報の`key`として未読/既読を制御するフラグ(`read`)が含まれている
* フラグ(`read`)の`value`の型の評価
* 他のFieldが追加されていないことを確認する為の`size`の評価
* データが書き換えられていないことを確認する為の既存データとupdateされるデータでのフラグ以外のデータの一致の評価

ハマりやすそうな注意点として、update処理では、　`read`のみ追加しても、rule時には、`data.size()`は、ドキュメントのデータサイズになり、`request.data`に書き換えていないデータが含まれるところでしょうか。


### 補足

メッセージを投稿したユーザだけ削除可能にする評価

`match /messages/{messageId}`　配下に以下を追加します.

```
       allow delete: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/rooms/$(roomId))
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　&&　resource.data.userId == request.auth.uid; 
```