Firestoreセキュリティルールを設定してみる

# 0. 始めに
(本記事は、拙作 [Firestoreセキュリティルールを読み解く](https://qiita.com/hiroki-harada/items/99d13efe432eb89e9391) の続きになっています！)
前回で、Firestoreにデフォルトで設定されているルールを読み解いたので、今回は自分でルールを設定してみようと思います。
今回は、読み書きに対して正しくルールが設定されているかを見ていきます。

０．[Firestoreセキュリティルールを読み解く](https://qiita.com/hiroki-harada/items/99d13efe432eb89e9391)　← 前回
１．Firestoreセキュリティルールを設定してみる ← 今回★

# 1.準備
動作環境ですが、筆者は以下の環境で動作確認しています。
　OS : Windows10 Home
　Node : v11.13.0
　yarn : v1.22.4

また、事前にFirebseプロジェクトの作成を行っておきます。
(詳細な手順は述べませんが、プロジェクト作成について詳しく知りたい場合は、参考までに拙作の記事をご覧ください：[Firebaseでデプロイしよう！](https://qiita.com/hiroki-harada/items/ca22ac177db68e3c3796))


# 2. モデルとなるサイトの用意
　Firestoreにデータを登録するための入力フォームと、登録されたデータを表示するためのリストが付いたサイトをサクッと用意します[^20]。一番上のフォームに何かしら文字を入力して、「送信」を押すことでfirestore側に登録され、「更新」でfirestoreに登録されている全データが表示される仕組みになってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314204/b40f18a2-947a-5acf-ddf7-b7210d589eb8.png)

入力と表示のコードはこんな感じ

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>firestore-otameshi</title>
</head>
<body>
    <form action="submit">
        <input id="addData" type="text">
    </form>
    <button type="button" onclick="addData()">送信</button>
    <button type="button" onclick="getData()">更新</button>
    <h5>Firestore-dataList</h5>
    <div id="dataList"></div>

    <script src="https://www.gstatic.com/firebasejs/7.22.0/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/7.22.0/firebase-firestore.js"></script>
    <script src="./index.js"></script>
    <script src="./config.js"></script>
</body>
</html>
```
Firestore側へ登録データ取得が失敗したら、エラーログを表示する仕組みにしています(参考2より)。
firebaseのAPI KEYは何となく別ファイルで用意しておきました[^25]。

```javascript:index.js
// 受け取ったHTMLを表示
dispData = (html) => {
    document.querySelector("#dataList").innerHTML = html
}

// フォームのデータをfirestoreに追加
addData = () => {
    const inputData = document.getElementById("addData")

    if (!inputData.value) return
    firebase.firestore().collection("dataList").add({
        text: inputData.value
    })
    .then(() => {
        console.log("success!")
    })
    .catch((error) => {
        dispData(`データの追加に失敗しました (${error})`)
    })
}

// firestoreからデータを取得
getData = () => {
    firebase.firestore().collection("dataList").get().then((querySnapshot) => {
        let html = "<ul>"
        querySnapshot.forEach((doc) => {
            const data = doc.data()
            html += `<li>${data.text}</li>`
        })
        html += "</ul>"

        dispData(html)
    })
    .catch((error)=> {
        dispData(`データの取得に失敗しました (${error})`)
    })
}
```
```javascript:config.js
// Initialize Firebase
firebase.initializeApp({
    apiKey: "XXXXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "firestore-otameshi.firebaseapp.com",
    databaseURL: "https://XXXXXXXXXXXXXXXXX.firebaseio.com",
    projectId: "firestore-otameshi",
    storageBucket: "XXXXXXXXXXXXXXXXXXXX.appspot.com",
    messagingSenderId: "XXXXXXXXXXX",
    appId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    measurementId: "XXXXXXXXXXXXXXXXXXX"
})
```


# 3. ルール設定前の挙動
まずは、read, writeの両方を許可した状態でデータの登録と取得を行います。
設定するルールはこちら↓[^30]　ほぼデフォルトのルールですが、`dataList`という名前のコレクションに対して適用されるように、コレクション名とその内部のドキュメントを表す変数を`data`としておきます

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /dataList/{data} {
      allow read, write: if true;
    }
  }
}
```
(ホントはgifを貼りたいですが、)上記のルールだと登録も取得もうまくいきます。

# 4. ルール設定後の挙動
続いて、ルールを設定してみます。まずは、書き込み禁止から。

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /dataList/{data} {
      allow read: if true;
      allow write: if false;
    }
  }
}
```
フォームに入力して送信を押すと、確かに権限がなくてエラーが発生している事が分かります(上記で設定したエラー文言が表示されています)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314204/a714475a-aeec-b385-db47-bc6a93a86d39.png)

また下記のように記述する事で、全く関係のないコレクション(`dummyList`)に対してルールを設定しても、`dataList`には適用されない事も確認できます[^40]。

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /dataList/{data} {
   		allow read, write: if true;
    }
    match /dummyList/{data} {
      allow read: if true;
      allow write: if false;
    }
  }
}
```

続いて読み込み権限を付与していない場合も同様に確認できます

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /dataList/{data} {
   		allow write: if true;
      allow read: if false;
    }
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314204/00a5226f-31c3-3978-b97f-04b625ed9d0f.png)


# 終わりに
今回は`read`と`write`にフォーカスしていますが、2つはそれぞれ`get`と`list`、`create`, `update`, `delete`に細分化して、もっと細かな制御もできます。

# 参考
１．[Cloud Firestore セキュリティ ルールを構造化する](https://firebase.google.com/docs/firestore/security/rules-structure?hl=ja)
２．[[Firebase] Firestoreで読み書きする (Web編)](https://blog.katsubemakito.net/firebase/firestore_readwrite_1)


[^refs]: 
[^20]: と言いつつ、作るのにかなり時間が掛かってしまったのでした(笑)
[^25]: とは言え、API KEYのスコープがグローバルになっているの、良くない気がする
[^30]: どの言語のシンタックスハイライト使ったものか、よくわからなかったのですが、何にしたものでしょう。。。？
[^40]: 更に、全くルールを設定していないコレクションには、全ての操作が禁止されます