Firestoreでチャットルームを作る時のSecurity Rulesを考えてみた。
Firebaseの新しいサービスFirestoreを使って、チャットルームを作成する時のSecurity Rulesを検討してみました。
前提として、以下のようなチャットルームを想定。
- 1対1
- 書き込みは削除できない
- 未読/既読の状態を持つ
Security Rulesのベースは、Firestoreのマニュアルに参考になる記載があり、今回は、それを発展させて検討しました。
検討結果の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を持つ構成は、マニュアルに記載されている通り。
追加の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;