Firebase Hosting の一部だけ IP でアクセス制限する

内容としては[これ](https://qiita.com/akagire/items/4821c62d5b373f38cc36)の発展形です。特定のフォルダ内に別なNuxt.jsプロジェクトが入っており、そこをIP制限します。

```
/index.html   // baseurl のリクエストをさばくファイル
/_nuxt        // baseurl にリクエストされた時に読み込まれるスクリプト
/restrict     // baseurl/restrict にきたアクセスを IP 制限したい
  /index.html // baseurl/restrict のリクエストをさばくファイル
  /_nuxt      // baseurl/restrict にリクエストされた時に読み込まれるスクリプト
```

# TL;DR
認証の窓口となる express サーバーを Cloud Functions で動かして、静的ファイル（今回はビルドされたNuxt.js）をホスティングする。

# 前提条件
`/restrict` 配下にアクセスが有った場合のみ IP 制限を実施し、それ以外については制限をかけない。

# 詳細
## IP 制限する Cloud Functions を用意
Firebase は express のインスタンスを `onRequest` で渡せばよしなに動いてくれるので、IP 制限する express サーバーを用意します。こちらを参考にしました。
https://qiita.com/otakky/items/1363e7b4c706dc9cd096

```ipAuth.ts
import * as functions from 'firebase-functions';
import * as requestIp from 'request-ip';
import * as express from 'express';

// ここにアクセスを許可したいIPを記載
const allowedIps = ['192.168.1.1', ...];

const app = express();
app.all('/*', (req, res, next) => {
  const clientIp = requestIp.getClientIp(req);
  const isAllowed = allowedIps.indexOf(clientIp) !== -1;

  if (!isAllowed) {
    res.status(statusCode).send(`You cannot access here. Please check your IP. `);
  } else {
    next();
  }
});
// ホスティングしたい静的ファイルが格納されたフォルダを指定
app.use('/restrict', express.static(`${__dirname}/../restrict/`));

export const ipAuth = functions.https.onRequest(app);
```

## Firebase Hosting の設定を変更

`rewrite` ルールを使って、経由させたい関数を指定する。

```firebase.json
"hosting": {
  /* 中略 */
  "rewrites": [
    {
      "source": "/restrict{,/**}",
      "function": "ipAuth"
    },
    {
      "source": "**",
      "destination": "/index.html"
    }
  ]
  /* 中略 */
}
```

全体を IP 制限したいのであれば、 2つ目のルールの `"destination"` を `"function": "ipAuth"` にしてあげればよいです。

## Nuxt.js の設定
Nuxt.js のビルド結果は絶対パスで生成されるため、ローカルで直接開けば正常に動きますが、ホスティングすると `https://BASEURL/_nuxt` を参照してしまい、 `/restrict` 向けのビルド結果を参照できません。それを是正するため、 baseルートを指定します。

```nuxt.config.js
export default {
  // 略
  router: {
    base: '/restrict/'
  },
  // 略
}
```
これで、`dist` にビルドされる `index.html` の script タグの参照先は `/restrict/_nuxt/hogehoge...js` になり、ホスティングされても正しく解釈されるようになります。

## デプロイ前調整
`/restrict` でアクセスされる Nuxt.js のビルド結果を IP 制限用 Cloud Functions の静的ファイルが指し示す場所 ``${__dirname}/../restrict/`` にコピーしてください。

## デプロイ
通常通り `firebase deploy` で大丈夫です。

# ハマりどころ
## express-ipfilter を使ってはいけない
何故か Firebase の内部 IP を拾ってしまうようで、どこからアクセスしても 自分のグローバル IP ではないもいの（しかも IPv6 ) を拾ってしまいフィルター出来ませんでした。

## express サーバーのパスの指定漏れ
```ipAuth.ts
app.use('/restrict', express.static(`${__dirname}/../public/restrict/`));
//       ~~~~~~~~~ これ
```

全体をIP制限したい場合は未指定 `app.use(express.static(...))` でいいのですが、今回は `/restrict` にアクセスした場合のみ制限するため、これがないとエラーになります。

## Hosting の設定
`rewrites` ルールで `"source": "/restrict"` と書いてしまうと、`/restrict/_nuxt/hogehoge...js` すら `/restrict` にリダイレクトされてしまい、内容が `index.html` になっている JS が配信されてしまいます。

# その他
同じ感じで BASIC 認証も出来ます。express で BASIC 認証する方法を調べれば良いと思います。

# デメリット
アクセスする度に `Function execution started` と `Function execution took xx ms, finished with status code: 200` のログが出力されるので Stackdriverが 汚されます。別にログ取らなくていいんだけどなぁ。。。

それでは！

# 2020/10/1追記: 他のやり方

身もふたもないですが、プロキシサーバーを用意して、IP制限したいコンテンツだけ別サーバーでホスティングするというのが多分最適解です。
この記事を書くに至ったプロジェクトはプロキシサーバー方式に変更する予定です。