firebase realtime database でよく使う rule

## 概要

firebase realtime database (以下 database) には rule という仕組みがあり、これを用いることである程度のデータ構造(RDB で言う所のテーブル構造)を定義したり、特定のデータへのアクセス制限をしたりすることができます。

基本的な概念や使い方は[公式ページ](https://firebase.google.com/docs/database/security/)が日本語化されているのそちらを参照してください。

## ノウハウ集

firebase を使って4つほどアプリケーションを作ってみてよく使った rule やこうした方が良いみたいなパターンを書いていきたいと思います。

## 認証しているユーザーは全てのデータにアクセス許可

database の rule はデフォルトでは以下のように認証済みのユーザーは全てのデータに read, write の権限を持っています。 

`auth` というのは実は変数で認証していると `uid` などのプロパティを持つオブジェクトになっています。

セキュリティなどが心配ですがプロジェクトの最初の段階ではこの状態でｶﾞｼｶﾞｼ開発していっても良いと思います。

```json 
{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}
```

### 全部拒否 (おすすめの設定)

これは僕のおすすめの設定です。

デフォルトの状態のまま使うのを止めて以下のように read, write を false にして全てのデータにアクセスできなくします。

```json
{
  "rules": {
    ".read": false,
    ".write": false,
  }
}
// この状態でも nodejs 等の admin 用ライブラリではデータにアクセスすることができます。
```

こうしておくことで以下のような利点が生じます。

- 明示的にアクセスを許可したデータだけユーザーがアクセスできるようになるのでセキュリティが高くなる
- データを保存する際に key を typo してもその key には書き込み権限が無いとエラーが返ってくるのでプログラムのバグを防げる
  - ex) `text` を `test` と typo したときなど

> ちなみに rule に何も書かないと上記と同じ意味になります。

### 特定の key だけ認証済みユーザーは許可

アプリケーションを作る上では root で全て拒否しておいて、任意のデータにだけアクセスは許可したいというのが基本的な使い方だと思いますのでこれは絶対に抑えておきましょう。

以下の例では users 以下のデータには認証済みユーザーがアクセスできるという例です。

```json
{
  "rules": {
    "users": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
  }
}
```

bolt で書くとこうです。

```bolt
path /users {
  read() { auth != null }
  write() { auth != null }
}
```

または以下のようにすると `userId` の指定を忘れて `users` 直下にデータを書き込んじゃったみたいなミスが無くなります。

`$userId` は[組み込み変数](https://firebase.google.com/docs/database/security/securing-data#predefined_variables)と言われるものの一種で `$` 以降の `userId` の部分はただの変数名なのでなんでも大丈夫です。

```json
{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null"
      }
    }
  }
}
```

bolt で書くとこうです。 ネストが深くならなくていいですね。

```bolt
path /users/{userId} {
  read() { auth != null }
  write() { auth != null }
}
```

### 自分のデータにだけアクセス許可

先の例 (users) では他のユーザーの情報もアクセスが許可されていますが、自分のデータだけしかアクセス許可しないというシーンもあると思います。

この場合は組み込み変数の `$userId` を利用して以下のように `auth.uid == $userId` とすると自分のデータのときだけアクセスできるようになります。

```json
{
  "rules": {
    "user-data": {
      "$userId": {
        ".read": "auth.uid == $userId",
        ".write": "auth.uid == $userId",
      }
    }
  }
}
```

bolt で書くとこうです。

```bolt
path /user-data/{userId} {
  read() { auth.uid == userId}
  write() { auth.uid == userId }
}

```

### 書き込みできるのは自分のデータだけ

書き込みと読み込みのアクセス権限を分けることももちろんできます。

以下の例は読み込みは認証済みのユーザーなら誰でも出来るけど、書き込みは自分だけできるというアクセス権限です。 

```json
{
  "rules": {
    "user-data": {
      ".read": "auth != null",
      "$userId": {
        ".write": "auth.uid == $userId",
      }
    }
  }
}
```

### 管理者ユーザーなら読み書きできる

以下のようなデータがあったとして、 `uid_10` のデータに本人と管理者ユーザー(uid_1 or uid_2)がアクセスできるという権限を作る方法です。

管理画面などで特権ユーザーの場合にはなんでもできるようにしたいというシーンです。

```json
{
  "admin-users": {
    "uid_1": true,
    "uid_2": true,
  },
  "user-data": {
    "uid_10": {
       "hoge": "huga"
     }
  }
}
```

書き込み権限の `root.child('admin-users').child(auth.uid).exists()"` の部分がミソです。

`root` という組み込み変数で database の root の [RuleDataSnapshot](https://firebase.google.com/docs/reference/security/database/#ruledatasnapshot) というオブジェクトが取得できます。 これを使うと query を発行できるので他の key のデータを参照してロジックを組むことができます。

今回の場合は `admin-users` に現在の user の uid があったら管理者でログインしているとみなして書き込みを許可するというロジックを組んでいます。

rule の中にコードが書けてすごい便利です。

```json
{
  "rules": {
    "user-data": {
      ".read": "auth != null",
      "$userId": {
        ".write": "auth.uid == $userId || root.child('admin-users').child(auth.uid).exists()",
      }
    }
  }
}
```

`user-data` に限らずなんでも出来るようにする場合は、 rules のルートに書いても良いです。

```json
{
  "rules": {
    ".read": "root.child('admin-users').child(auth.uid).exists()",
    ".write": "root.child('admin-users').child(auth.uid).exists()",
    "user-data": {
      ".read": "auth != null",
      "$userId": {
        ".write": "auth.uid == $userId",
      }
    }
  }
}
```

これらの方法はデータのアクセス制限を作る上でめちゃめちゃよく使うパターンです。

### 特定のデータは必須にする

例えば users の中のデータには `full_name` と `created_at` が必須という制限を設けたいときは ".validate" を使用して以下のように記述します。

```json
{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        ".validate": "newData.hasChildren(['full_name', 'created_at'])"
      }
    }
  }
}
```

### 型を限定する

型と言っても js なのでそんなに厳密に扱えるわけではありませんが、  `isNumber()`、 `isString()`、 `isBoolean()` を使うことができます。 [詳細はこのあたりを参照してください。](https://firebase.google.com/docs/reference/security/database/#isnumber)

以下の例では `full_name` は文字列であるという制限を設けています。

```json
{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        "full_name": {
          ".validate": "newData.isString()"
        }
      }
    }
  }
}
```

### いずれかの値に制限する (enum のような使い方)

ある key に入るデータはある集合のうちのどれか１つをとるという制限を設けたい場合があります。

以下の例ではユーザーの `type` を `private` または `public` のどちらかにしたいという制限を設けています。

```json
{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        "type": {
          ".validate": "(newData.val() === 'private' || newData.val() === 'public')"
        }
      }
    }
  }
}
```

### インデックスを張る

database にはインデックスを貼ることができます。 インデックスは value を使用したクエリーを発行する場合に適切に設定されていないと警告が出るので、それが出たら設定するという感じでいいと思います。

firebase は非常に賢いです。

インデックスは以下のように `.indexOn` で設定します。

```json
{
  "rules": {
    "users": {
      "$userId": {
        ".read": "auth != null",
        ".write": "auth != null",
        ".indexOn": ["full_name"]
      }
    }
  }
}
```

## まとめ

様々なケースを例に上げて rule を示しました。

firebase database は比較的新しいサービスなだけに rule の例がそんなに多くないので参考になれば幸いです。

他にも思いついたら追記していきたいと思います。

## 追記

realtime database の rule は json でそのまま書くと記述量が多く大変になります。

下記の Bolt Compiler を使って記述すると短く書けてとても良いのでお試しください。

https://github.com/firebase/bolt