かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Node.jsでFetchAPIを使いたい

node v11.4.0

fetchを使ったJavaScriptファイルをnodeの実行しようとしたらfetchなんて無いよってエラーになりました。
node.jsには現状デフォルトでFetchAPIが入っていないようです。

node-fetch を使う

$ yarn add node-fetch

JavaScriptファイルでnode-fetchを読み込んで後はブラウザと同じ使い方

const fetch = require('node-fetch');

fetch(url, {method: 'GET'})
  .then((res) => console.log(res))
  .catch((err) => console.error(err));

importを使う場合は拡張子を.mjsにする

どのみちインストールが必要なんでfetchにこだわらないなら、axiosとかでも良さそうです。


[参考]

WEB+DB PRESS Vol.108

WEB+DB PRESS Vol.108

  • 作者: 中野暁人,山本浩平,大和田純,曽根壮大,ZOZOTOWNリプレースチーム,権守健嗣,茨木暢仁,松井菜穂子,新多真琴,laiso,豊田啓介,藤原俊一郎,牧大輔,向井咲人,大島一将,上川慶,末永恭正,久保田祐史,星北斗,池田拓司,竹馬光太郎,粕谷大輔,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/12/22
  • メディア: 単行本
  • この商品を含むブログを見る

[WIP] Firebase Cloud Firestore 接続情報が漏れるとパケ死しないか気になっていたので調べてるメモ

最近Compass漁ってNuxt+Firebase とかReactNative(Expo)+FirebaseとかのHands-onに色々と参加していました。

フレームワークで生成した静的ファイル(HTML+CSS+JavaScript)をFirebaseやNetlifyにデプロイすれば簡単にFirebaseと連動したWEBサービス・アプリがが作れてマジヤバイ (語彙力 という感じです。

しかしながら僕のような古(いにしえ)のほうむぺぃじ作ってきたマンとしては、firebaseとか従量課金だと設定漏れて悪用されたらパケ死しない???という不安もあり、生成されたものがブラックボックスなままでは

  • 静的ファイルならどうやってFirebaseと接続してるの?
  • 接続に必要な設定ファイルはどういう扱いになっているんだろう?
  • JavaScriptで読み込むとローカル側に設定ファイルDLするよね?

というような疑問が残ったままになっていました。

Nuxt generate で生成された静的ファイルの中身

Nuxtが吐き出した静的ファイルのディレクトdist内でagコマンド(The Silver Searcher)を使ってfirebase apiKeyを検索。
-lオプションを付けるとマッチしたファイル名だけ返してくれます。

$ cd dist
$ ag -l <firebase apiKey>
_nuxt/cf55170e35703f7a131c.js

nuxt generateで生成されたJavaScriptファイルに.envに書いてたFirebaseの接続情報がバンドルされているようです。

firebase serve コマンドを実行すると、Firebaseのdeploy先に指定していたディレクトリをルートにしたローカルサーバーで確認ができるようです。
これで先程のfirebase apiKeyが含まれているファイルが使用されているか見てみます。

$ firebase serve
i  hosting: Serving hosting files from: dist
✔  hosting: Local server: http://localhost:5000

ブラウザhttp://localhost:5000にアクセスして、devtoolを開いて確認します。
f:id:kikiki-kiki:20190122075825p:plain
読み込まれていました。

やはり静的化されたものでは直接設定ファイルを読み込んでFirebaseにアクセスしているようです。

Firebase Hosting だと接続情報はそもそも見ることが出来る

Firebase Hostingを使っているとhttps://{Hosting Domain}/__/firebase/init.jsonで接続情報が見れるみたいです。

Firebase Hosting は、/__ で始まるサイトの URL を予約します。この予約された名前空間により、Firebase Hosting と他の Firebase 機能を簡単に同時利用できます。予約済みの URL は、デプロイ時だけでなく、firebase serve をローカルで実行したときにも利用されます。
SDK の自動構成
SDK 自体をホスティングするだけでなく、予約済みの名前空間では、Firebase Hosting サイトに関連するプロジェクトの SDK の初期化に必要な構成も提供されます。これは、直接追加できるスクリプトとして提供されます。

<!-- load Firebase SDK before loading this file -->
<script src="/__/firebase/init.js"></script>

出典: 予約された URL  |  Firebase

アプリケーション側からCloud Firestoreに新しいコレクションを作成することが出来るのか?

接続情報は誰でも見れる状態なので、テストモードの誰でも読み書きできるデータベース設定・ドメインホワイトリストを設定していない状態で、アプリケーション側からCloud Firestoreに新しいコレクションを作成できるのか調べてみました。

https://{Hosting Domain}/__/firebase/init.jsonで取得できた接続情報を元に次のような存在しないコレクションevil-tableにデータを追加しようとするスクリプトのあるHTMLを作成しました。


<html>
<body>
<script src="https://www.gstatic.com/firebasejs/5.8.0/firebase.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.12.1/firebase-firestore.js"></script>
<script>
// Initialize Firebase
var config = {
  apiKey: "<init.jsonにあるapiKey>",
  databaseURL: "<init.jsonにあるdatabaseURL>",
  storageBucket: "<init.jsonにあるstorageBucket>",
  authDomain: "<init.jsonにあるauthDomain>",
  messagingSenderId: "<init.jsonにあるmessagingSenderId>",
  projectId: "<init.jsonにあるprojectId>"
};
firebase.initializeApp(config);

const db = firebase.firestore();
// 存在しないコレクションにデータを追加してみる
db.collection("evil-table").add({
  name: 'new item'
})
.then((res) => console.log("Document written with ID: ", res.id))
.catch((err) => console.error("Error adding document: ", err))
</script>
</body>
</html>

これを適当なドメインを割り当てているサーバにおいてアクセスしてみます。
コンソールをみてみると...

> Document written with ID:  QHHn09EqgvNvZxSifBdU

データ作れてるっぽい。
FirebaseコンソールにアクセスしてDatabaseの項目を見てみます。
f:id:kikiki-kiki:20190122130932p:plain
データ作られちゃってますね... (゚∀゚)アヒャャャャャャ

だでれも読み書きできるテストモードのままだと、誰でも新しいDBのテーブルが作れるちゃうので他人のFirebaseアカウントに寄生したアプリとか作れちゃうのでは🤔

Firebase の接続情報より権限設定の方が重要っぽい

Firebase リリース チェックリスト
WEB: 不正使用を防ぐためのドメインホワイトリストを追加します。 - Google Developer Console のブラウザ API キーとクライアント ID の運用環境ドメインホワイトリストに登録 - Firebase コンソール パネルの [Auth] タブにある運用環境ドメインホワイトリストに登録
Cloud Firestore
意図しないデータアクセスを防ぐためにセキュリティ ルールを構成します。
出典: Firebase Launch Checklist  |  Firebase

cf.

The apiKey essentially just identifies your Firebase project on the Google servers. It is not a security risk for someone to know it. In fact, it is necessary for them to know it, in order for them to interact with your Firebase project.
In that sense it is very similar to the database URL that Firebase has historically been used to identify the back-end: https://<app-id>.firebaseio.com. See this question on why this is not a security risk: How to restrict Firebase data modification?, including the use of Firebase's server side security rules to ensure only authorized users can access the backend services.
出典: javascript - Is it safe to expose Firebase apiKey to the public? - Stack Overflow

まとめ

Hands-onで静的ファイルの場合は設定ファイルをサーバーに上げて直接アクセスはできなくする。FirebaseにHostingする場合はFirebase側に設定があるから不要。Nuxtの場合はプロジェクト生成時に.envdist/*が自動的に.gitignoreされるのでFirebaseの設定ファイルがGitHubに上がったりすることはないので安心と聞いていたのですが、そもそもFirebaseの設定は見れても問題がなくて、むしろCloud Firestoreのルール設定や不正使用を防ぐためのドメインホワイトリストの設定の方が重要な印象を受けました。接続情報のモーダルがそもそも、scriptタグに囲まれたのをHTMLに貼り付けてくださいって出てきてますものね...

なのでHands-onなどでDatabaseはテストモードのままサクッと作ったアプリを雑に公開していていたり、接続情報が入ったファイルをGitHubにpushしていたりして、悪意のある誰かに見つかってしまうと接続情報からDBに寄生したアプリに利用されたり、遊び半分に大量のデータを投稿するボット作られちゃったりで、パケ死する可能性がなくもないのでは?と感じました。

サクッとアプリが作れるのはすごいのですが、Cloud Firestoreのルール設定やFirebaseの請求のアラート設定などは、公開をゴールにするHands-onならしっかり説明しておいて欲しいなって思いました。
Firebaseセキュリティ設定のHands-onぜひ開催してください!!


[参考]

firebaseの設定関連の参考サイト

WEB+DB PRESS Vol.105

WEB+DB PRESS Vol.105

  • 作者: 小笠原みつき,西村公宏,柳佳音,志甫侑紀,池田友洋,木村涼平,?橋優介,大塚雅和,飯塚直,吉川竜太,末永恭正,久保田祐史,浜田真成,穴井宏幸,大島一将,桑原仁雄,牧大輔,池田拓司,はまちや2,竹原,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/06/23
  • メディア: 単行本
  • この商品を含むブログを見る

実践Expo React NativeとFirebaseで、SNSアプリを最速ストアリリース! (NextPublishing)

実践Expo React NativeとFirebaseで、SNSアプリを最速ストアリリース! (NextPublishing)

Nuxt で Firebase を使う (Cloud Firestore & Hosthing)

株式会社ROLOさんが主催しているJavaScript Buildersで受けたHands-onでやったことを復習も兼ねて調べながらメモ。

1. Nuxtのプロジェクトを作成

$ npx create-nuxt-app <Project Name>
$ cd <Project Name>

create-nuxt-appをインストールした覚えがないのですが、npxコマンドだとそのままプロジェクトが作成できるっぽい!?

Nuxt.jsの起動

$ yarn run dev

ブラウザでhttp://localhost:3000にアクセス

Nuxt.jsで静的ファイルの生成

$ yarn run generate

2. プロジェクトをFirebaseに接続する

Firebaseのコンソールから新規プロジェクトを作成している前提。

2-1. Firebase CLIのインストール

Firebase CLIは、Firebase プロジェクトの管理、表示、デプロイを行うためのさまざまなツールを提供します。

firebaseを使うプロジェクトを今後もつくりそうならglobalにインストールしてしまって良いと思います。

$ npm install -g firebase-tools

2-2. FirebaseアカウントにPCを接続

$ firebase login

コマンドを実行するとgoogleアカウントの選択画面が表示されるので、Firebaseを使用しているアカウントを選択してFirebase CLIに許可を与える

2-3. Firebaseの設定をプロジェクトに追加

NuxtでFirebaseを使いたいので、1.のcreate-nuxt-appで作成したNuxtプロジェクトディレクトリのトップで下記コマンドを実行

$ firebase init

firebase init コマンドでは、新規ディレクトリは作成されません。新しいプロジェクトをゼロから開始する場合は、先にディレクトリを作成し、その新しいディレクトリに移動してから、init コマンドを実行する必要があります。
出典: Firebase CLI リファレンス  |  Firebase

コマンドを実行するとターミナル上で色々と訊かれます。

? Which Firebase CLI features do you want to setup for this folder? Press Space to sele
ct features, then Enter to confirm your choices.

どのサービス使うか
上下ボタンで使いたいサービスに移動してスペースキーで選択 ※ 日本語入力になっているとスペースで選択できないので注意
データベース(firestore)とホスティング(Hosting)を選択

? Select a default Firebase project for this directory

どのFirebaseプロジェクトを利用するか
予め作っておいたプロジェクトを選択

? What file should be used for Firestore Rules? (firestore.rules)

データベース(firestore)のルールファイル名は何にするか

? What file should be used for Firestore indexes? (firestore.indexes.json)

データベース(firestore)のインデックスのファイル名 (インデックスファイルはちょっと理解できてない

? What do you want to use as your public directory? (public)

FirebaseのHosthingにアップして公開するディレクト
Nuxtの場合は静的ファイルの生成がdistディレクトリになるのでdistと入力

? Configure as a single-page app (rewrite all urls to /index.html)? (y/N)

SPA様に設定するか

質問が終わるとFirebase関連のファイルが生成される

  • .firebaserc
  • firebase.json
  • firestore.indexes.json
  • firestore.rules
  • dist/index.html

2.4. Firebaseにデプロイ

下記コマンドで、デプロイするディレクトリに指定した./dist内がアップロードされる

$ firebase deploy

3. Firebaseの接続情報を.envファイルから読み込めるようにする

Nuxt.js の dotenv-module を利用する事で、こういった環境個別の設定情報を .env ファイルを用いて柔軟に切り替えることが出来るようになります。
.env 内の変数情報は、Nuxt 内にて、 process.env.API_URL のようにして参照する事が可能です。
出典: Nuxt.js で dotenv を活用する – chatbox.blog

@nuxtjs/dotenvのインストール

$ yarn add @nuxtjs/dotenv

3-1. Nuxtでdotenvモジュールの使用を設定

nuxt.config.jsmodulesに設定を追加

module.exports = {
  /*
  ** Nuxt.js modules
  */
  modules: [
    '@nuxtjs/dotenv',
  ],
}

3-2. .envファイルにfirebaseの接続情報を記述

.envファイルの作成

$ touch .env

firebaseプロジェクトの設定から</>アイコンをクリックして表示される接続情報を.envファイルに設定します。
.envファイルはNuxtのプロジェクトでは自動的に.gitignoreされているので間違ってgit pushしてしまう心配が無いそうです!

f:id:kikiki-kiki:20190120121845p:plain
</>をクリックすると接続情報がモーダルで表示される

FB_API_KEY=<FIREBASE_API_KEY>
FB_AUTH_DOMAIN=<FIREBASE_ AUTH_DOMAIN>
FB_DATABASE_URL=<FIREBASE_DATABASE_URL>
FB_PROJECTID=<FIREBASE_PROJECT_ID>
FB_STORAGE_BUCKET=<FIREBASE_ STORAGE_BUCKET>
FB_MESSAGING_SENDER_ID=<MESSAGING_SENDER_ID>

3-3. Nuxtでfirebaseの接続をする

pluginsディレクトリにFirebaseと接続するためのプラグインを作成する
Nuxtでは(Vueでは?)自前のライブラリはpluginsディレクトリ内に置くお作法っぽいです。

plugin/firebase.js

import firebase from "firebase";

// .env に設定した値を取得してる
const config = {
  apiKey: process.env.FB_API_KEY,
  authDomain: process.env.FB_AUTH_DOMAIN,
  databaseURL: process.env.FB_DATABASE_URL,
  projectId: process.env.FB_PROJECTID,
  storageBucket: process.env.FB_STORAGE_BUCKET,
  messagingSenderId: process.env.FB_MESSAGING_SENDER_ID
}

if (!firebase.apps.length) {
  firebase.initializeApp(config)
}

export default firebase

nuxt.config.jsプラグインのパスを追加
nuxt.config.js

module.exports = {
  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
    '~/plugins/firebase',
  ],
}

Firebaseを使用するページのテンプレートでプラグインを読み込み pages/index.vue

<script>
import firebase from "~/plugins/firebase.js"
</script>

nuxt.config.jsプラグインのパスを書けば自動的に全ページで使えるようになる訳ではなく、 プラグインを使用したいページで都度importする必要があるようです。

nuxt.config.jsにパスを書くのは静的化したりする時に何をbundlするかってのをwebpackみたいに明示化してるって感じなのでしょうか?(理解しきれてない部分です。

fc. プラグイン - Nuxt.js

4. Cloud Firestoreのデータにアクセス

ページからFirebaseのリアルタイムデータベースCloud Firestoreに接続してデータ/追加を取得します

pages/index.vue

<script>
import firebase from "~/plugins/firebase.js"

const db = firebase.firestore();
const settings = { timestampsInSnapshots: true };
db.settings(settings);

// データの取得(全件)
const getItems = async () => {
  return db.collection('<collection_name>')
    .orderBy('createdAt', 'asc')
    .get();
};

// データの追加
const addItem = async (content) => {
  return db.collection('<collection_name>').add({
    ...content,
    createdAt: new Date(), // createAt フィールドがないとエラーが表示されるっぽい。
  })
  .then((res) => {
    console.log('Add Document with ID:', res.id);
    return res.id;
  })
  .catch((err) => {
    console.error('Error: Add Document', err);
    throw err;
  });
};
</script>

これでFirebaseとの連携はできているので、データの取得・追加メソッドを適時実行するようにすればOK
yarn run devしておけばホットリロードで作っている最中のものが確認できます。
Cloud Firestoreを使ったアプリが完成したら下記コマンドで静的ファイルを生成して、Firebaseにデプロイ。

$ yarn run generate
$ firebase deploy

まとめと感想

本当に簡単にFirebaseと連携したものを作り公開まですることができました!!(2,3時間程度で)
新しいことが出来るようになるって、たーのしー!
色々なサービスを使って簡単に取りあえず動くものが作れるってのはモチベーションの維持にもとても良いですね。

と、同時に設定ファイルのことや、Nuxtフレームワークのお作法とか、まだまだ理解できてない部分もあり特にVue.jsを理解してないとNuxtのお作法の理解は難しそうだなーって部分と静的化されたもファイルの中を見ておらずブラックボックスなままで、どうやって静的なHTML+CSS+JavaScriptからFirebaseに接続しているのかって部分を理解しなければと新しい課題も見つかりました。
素晴らしいHands-onを開催してくれて本当にありがたい。

情報格差について感じたことのポエム(書いているうちに長くなったので読む必要なし)

東京 無料の勉強会もHands-onもめっちゃ多くてマジヤヴァイ。
インターネッツや本だけじゃなく、リアルに触れらる情報へのアクセスの容易さや身近に直に尋ねることが出来る人が多いって環境の格差があるとそりゃ田舎ちほーと差が生じるよっなーってほぼ限界集落の田舎ちほー送りになってたマンとしては実感してる日々です。ホントありがたい限りです。
ありがとうconnpass! (Doorkeeperのイベントは有料のが多い肌感

インターネットやSNSで情報得られるやろ!ってご意見もあるかと思いますが、インターネットは能動的に検索しなければ情報は得られず、探している情報以外は得にくいと感じています。
そして検索して出てきた情報が実際に使ってみてどうなのか?とかやり方が沢山出てくるようなジャンルではどういう思想でどのやり方を選択するのがスマートなのか?といった事を判断するのが特に知識の乏しいジャンルを学び始めた人にとっては難しいと思うのです。
故に身近に実際に使っている人が多く居て、その人達の意見を聞ける場が多いという環境は、検索ではでで来なかった別のアプローチなど予期せぬ知識を得られる場にもなっているという意味で、インターネットやSNSだけの環境より優位だと感じています。

つまり、本屋がない地域でもアマゾンがあれば本は買えるけど、本屋をぶらついて偶然目に入ったモノを立ち読みして良かったから買った。とか装丁が綺麗な本だったからジャケ買いした。というような偶然の出逢いは困難。というような感覚です。

モノの時代から、金融の時代になり、情報の時代を経て、知識の時代になるのであれば、知識に触れられる機会が用意な場所の方が優位になるのではないか、という感覚。
銃・病原菌・鉄」や「未来に先回りする思考法」とかに影響を受けているのでこんな感覚なのだと思うけれど。


[参考]

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発

Nuxt.jsビギナーズガイド―Vue.js ベースのフレームワークによるシングルページアプリケーション開発