🕒

[Firestore]作成日時、更新日時のつけ忘れを防ぐセキュリティルール

2022/02/19に公開

Firestoreに保存したドキュメントに作成日時・更新日時のフィールドを持っておくことは、運用の観点から重要です。ユーザの問い合わせがあった場合などに調査の手掛かりとするためです。

Firestoreでは、更新日時を登録するときにJavaScript側で値を設定することが多いと思います。そのような場合、開発が進むにつれ更新日時の設定忘れが発生する場合があります。セキュリティルールでそのような更新忘れを防ぎましょう。

サンプルアプリ

名前(name)、年齢(age)、作成日時(createdAt)、更新日時(updatedAt)を持っているオブジェクトをFirestoreに登録します。

{
  name: "たろう",
  age: 30,
  createdAt: 2022-02-20 12:34:56,
  updatedAt: 2022-02-25 16:12:34
}

JavaSriptのコード

JavaScript側のコードです。新規作成(create)ではcreatedAtupdatedAt、更新(update)ではupdatedAtserverTimestamp()を指定することがコツです。serverTimestamp()を指定するとサーバーが更新を受信した時刻が設定されます。

import { addDoc, collection, doc, serverTimestamp, updateDoc } from "firebase/firestore"

// 新規作成
const handleCreate = async (name, age) => {
  const docRef = await addDoc(collection(db, "users"), {
    name,
    age,
    createdAt: serverTimestamp(),
    updatedAt: serverTimestamp(),
  })
}

// 更新
const handleUpdate = async (id, name, age) => {
  await updateDoc(doc(db, "users", id), {
    name,
    age,
    updatedAt: serverTimestamp(),
  })
}

セキュリティルール

続いてセキュリティルールです。

firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
   function hasFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      // 必須フィールドが含まれているかつ必須とオプションフィールドのみ含まれていること
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    
    match /users/{id} {
      function validUser(docData) {
        return 
          hasFields(["name", "age", "createdAt", "updatedAt"], []) &&
          docData.name is string &&
          docData.name.size() <= 50 &&
          docData.age is int &&
          docData.age >= 20 &&
          docData.age <= 99 &&
          docData.createdAt is timestamp &&
          docData.updatedAt == request.time;
      }
      
      allow read: if true;
      
      allow create: if 
        validUser(request.resource.data) &&
        request.resource.data.createdAt == request.time;
      
      allow update: if
        request.resource.data.diff(resource.data).affectedKeys().hasOnly(["name", "age", "updatedAt"]) &&
        validUser(request.resource.data);
    }
  }
}

ポイントは、create時のrequest.resource.data.createdAt == request.timevalidUser内のdocData.updatedAt == request.timeです。request.timeには、サーバで処理された時間が入っています。
また、update時に、

request.resource.data.diff(resource.data).affectedKeys().hasOnly(["name", "age", "updatedAt"])

として、更新対象をnameとage、updatedAtに絞ることによってcreatedAtの上書を防いでいます。

以上でcreatedAt、updatedAtのつけ忘れを防ぐセキュリティルールができました。

環境

  • firebase:9.1.3
  • react: 17.0.2

Discussion