この記事はFirebase #2 Advent Calendar 2018の記事です。
はじめに
某webマーケティング会社でフロントエンドエンジニアとして働きながら、バンド活動もおこなっているtarooooooooooです。
バンド内での貸し借りを可視化するために「Cashica」というサービスを作っています。
詳細についてはこちらの記事からどうぞnuxt.js + firebase でPWA,SSRな借りパクを撲滅するアプリを作っている話
本記事では、上記サービスを作る際に学んだfirestore設計の知見を記載します。
設計
postsはドキュメントのIDはfirestoreによる自動割り当て、コレクションには貸し借りしているモノの詳細と、貸しているuser、借りているuserへの参照が入っています。
usersはドキュメントのIDにOauth認証の際に取得したuidを使っています。
ここでポイントとなるのが、usersのドキュメントIDを自動割り当てではなく、uidを用いているところになります。
こうしておくことで後々行うSecurity Ruleの設定が楽になります。
データの追加
データを追加するコードを見ていきましょう。(一部抜粋です)
userの追加
userにはfirebase.auth().onAuthStateChanged()
というメソッドで取得した認証情報が入っています。
db.collection('users').doc(user.uid).set({
uid: user.uid,
name: user.displayName,
email: user.email,
icon: user.photoURL
})
postの追加
from,toはそれぞれ貸すユーザ、借りているユーザのuidが入っています。
db.collection('users').doc(from)
のようにフィールドの値を設定すると参照型で保存がされます。
db.collection('posts').add({
from: db.collection('users').doc(from),
to: db.collection('users').doc(to),
description,
deadline,
createTime: firebase.firestore.FieldValue.serverTimestamp()
})
Security Ruleの設定
下記のテンプレートに追加する形でセキュリティルールを記載していきます。
- postsのドキュメントはフィールド内のto,fromに参照型で保存されているユーザのみが読み取り・更新・削除できる
- postsの投稿は認証されているユーザであればできる
- user情報の読み取りは認証されていればできる
- user情報の書き換えはuser本人のみができる
service cloud.firestore {
match /databases/{database}/documents {
}
}
postsのドキュメントはフィールド内のto,fromに参照型で保存されているユーザのみが読み取り・更新・削除できる
-
request.auth.uid
は認証が通っているuserのID、 -
resource.data.to
は読み書きしたいドキュメントのtoフィールドの値
usersのドキュメントIDを自動割り当てではなく、uidを用いているため以下のように、読み取りしたいドキュメントのto,fromの参照型の値と認証が通っているユーザへのpathとの比較が可能となります
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read, update, delete: if resource.data.to == /databases/$(database)/documents/users/$(request.auth.uid) ||
resource.data.from == /databases/$(database)/documents/users/$(request.auth.uid);
}
}
}
postsの投稿は認証されているユーザであればできる
isAuthenticated()
は認証されているかどうかを判断してくれるので下記のように追記します。
service cloud.firestore {
match /databases/{database}/documents {
function isAuthenticated() {
return request.auth != null;
}
match /posts/{postId} {
allow read, update, delete: if resource.data.to == /databases/$(database)/documents/users/$(request.auth.uid) ||
resource.data.from == /databases/$(database)/documents/users/$(request.auth.uid);
allow create: if isAuthenticated();
}
}
}
user情報の読み取りと、作成は認証されていればできる
allow get, create: if isAuthenticated();
とすることで可能になります。
service cloud.firestore {
match /databases/{database}/documents {
function isAuthenticated() {
return request.auth != null;
}
match /posts/{postId} {
allow read, update, delete: if resource.data.to == /databases/$(database)/documents/users/$(request.auth.uid) ||
resource.data.from == /databases/$(database)/documents/users/$(request.auth.uid);
allow create: if isAuthenticated();
}
match /users/{userId} {
allow get, create: if isAuthenticated();
}
}
}
user情報の書き換えはuser本人のみができる
request.auth.uid == resource.data.uid
で認証が通っているユーザと対象ドキュメントのuidとの比較ができ、同じ場合のみ許可します。
service cloud.firestore {
match /databases/{database}/documents {
function isAuthenticated() {
return request.auth != null;
}
function isUserAuthenticated(userId) {
return request.auth.uid == userId
}
match /posts/{postId} {
allow read, update, delete: if resource.data.to == /databases/$(database)/documents/users/$(request.auth.uid) ||
resource.data.from == /databases/$(database)/documents/users/$(request.auth.uid);
allow create: if isAuthenticated();
}
match /users/{userId} {
allow read: if isAuthenticated();
allow update, delete: if request.auth.uid == resource.data.uid;
}
}
}
おわりに
初めはセキュリティルールの設定のことを想定していなかったので、ルールを設定する段階でデータ構造の見直しが必要になってしまったので、初めからある程度想定しておいた方が幸せな気がしました。
サービスのDEMOとGitHubリポジトリは下記です。
GitHub: https://github.com/taroodr/cashica
Demo: https://easypay-dd04a.firebaseapp.com
参考
https://medium.com/google-cloud-jp/firestore2-920ac799345c
https://tech-blog.sgr-ksmt.org/2018/12/11/194022/
https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja
https://firebase.google.com/docs/firestore/security/get-started?hl=ja