https://qiita.com/sgr-ksmt/items/a7bda48535033f6a4b68
こちらの記事を読んで、updateTimeをクライアントからpostされてきたデータを使っている時点でもっとセキュアにできると思ったのでこの記事を書いています。
トリガーとは
詳細は、 Cloud Firestore トリガー を読んでもらうとして、要は Firestore のドキュメントが更新(追加削除含)されたときに発火したイベントをハンドルする関数 です。
Cloud Functionsで下記のような関数を作成すると、 posts
コレクションにドキュメントが追加されたイベントを購読できます。
functions.firestore
.document('posts/{postID}')
.onCreate(async (snap, context) => {
/* ... */
});
onCreate
onUpdate
onDelete
onWrite
があります。それぞれが購読するイベントはこちらを参照してください。
トリガー内での処理
ドキュメント追加時であれば、こんな感じ
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const Post = admin.firestore().collection('posts');
exports.createPostTrigger = functions.firestore.document('posts/{postId}').onCreate(async (snap, context) => {
const newData = Object.assign(snap.data(), {
createTime: snap.createTime,
updateTime: snap.updateTime,
// 他になにか入れたい場合はここに突っ込む
});
await Post.doc(context.params.postId).set(newData).catch((error) => {
console.error(error.code, error.message);
});
});
これで ドキュメントに createTime と updateTime に更新日時が入ります。
update には注意
post にレコードが追加された際に、発火した onCreate イベントによってドキュメントを更新した場合、 onUpdate イベントが発火してしまうので、 対策を行わないと無限に onUpdate の処理が行われてしまうので注意です。
今回はたまたま updateTime をドキュメントに設定しているので、更新前後の updateTime を比較して、異なっていれば更新せずに終了すればよいです。
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const Post = admin.firestore().collection('posts');
exports.updatePostTrigger = functions.firestore.document('posts/{postId}').onUpdate(async (snap, context) => {
if (!snap.before.data().updateDate) return;
const beforeUpdateTime = snap.before.data().updateTime.toDate();
const afterUpdateTime = snap.after.data().updateTime.toDate();
if (beforeUpdateTime.getTime() !== afterUpdateTime.getTime()) {
return;
}
const newData = Object.assign(snap.after.data(), {
updateTime: snap.after.updateTime,
});
await Post.doc(context.params.postId).set(newData).catch((error) => {
console.error(error.code, error.message);
});
});
snap
には更新前後のデータが入っているので、もし更新前のデータに updateTime がないということは onCreate 内の更新処理により発火したイベントであるといえるので、早期リターンします。
次に、更新前後の updateTime
を比較して、同じ時間ではない場合は、 updateTime が更新されたといえます。updateTime が存在し、更新前後で値が異なるという処理は、 updatePostTrigger
でしか行っていないので、この処理を続行する必要がないと言えるので、ここで早期リターンします。
ポイントとしては、 Firestore.timestamp
型同士の比較はうまく機能しません。 toDate()
で Date
型のインスタンスを取得して、そのオブジェクト同士を比較してください。それ以降は onCreate
時と同じです。
もちろん firestore.rules の設定は忘れずに!
updateTime
等がトリガーを経由せずとも登録できる状態ではこの対策をしたところでセキュアではないです。
リアルタイム性が必要なアプリの場合、この対策ではだめかも
Firestoreの更新→トリガーの発火にはそれなりにロードタイムが必要になります。リアルタイム性が求められる場合はこの対策は使えないかもしれません。
他にもっといい方法があれば教えてください!それではー。
2020/4/2追記
Trigger 使うの辞めました。理由は、更新が漏れるデータが定期的に発生していたためですログを仕込んでもエラーコード 13
しか帰って来ない。
同じような問題が起こっているケースも見つけました。
https://github.com/firebase/firebase-functions/issues/536
Firebase (gRPC) の問題ということで、お手上げです。。。今のうちは、おとなしくAPI経由で処理するしかなさそうですね。