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;