前提
NestJSプロジェクトの作成方法や、PassportとJWTを組み合わせて認証機構を実装する方法に関してはこちらを参照。
takagimeow.hatenablog.com
Firebase プロジェクトの作成方法についてはこちらの記事を参照。
takagimeow.hatenablog.com
Firebase FunctionsにVitestを導入する方法についてはこちらの記事を参照。
必要なパッケージをインストール
JWTを作成するためにjsonwebtoken
を入れる。
yarn add jsonwebtoken
テスト時に作成したjwtをデコードしたいので、jw
t-decode
パッケージを入れる。
yarn add -D jwt-decode
Firebase Functionsでfetch
を使用したいので、node-fetch
を入れる。
yarn add node-fetch
JWT文字列を生成する関数を作成する
createJwt
という関数を作成する。
jsonwebtoken
のsign
関数は、渡したペイロードを同期的にJSON Web トークン文字列に署名する関数である。
オプションで指定したalgorithm: "HS256"
は、HS256アルゴリズムを使用して署名するという意味である。
// src/utils/createJwt.ts
import {sign} from "jsonwebtoken";
export function createJwt(
payload: Record<string, string | number>,
secret: string
) {
const token = sign(payload, secret, {
algorithm: "HS256",
});
return token;
}
HS256とは
対象アルゴリズムと呼ばれていて、ひとつの鍵を署名側と検証側で共有するアルゴリズムである。なのでNestJSで使用しているシークレットとFirebase Functions側で同じ値を環境変数などを通して保有しておく。
RS256 と HS256 ってなにが違うの - Qiita
createJwt
関数に対するテストを作成する
先ほど作成したcreateJwt
関数に対して軽いテストを作成してみる。
作成したトークンが正しくデコードできて、その内容が元のペイロードと同じ内容を含んでいるかを試すだけの簡単なテスト。
// src/utils/createJwt.spec.ts
import {test, expect} from "vitest";
import jwtDecode from "jwt-decode";
import {createJwt} from "./createJwt";
// 環境変数と見立てる
const JWT_KEY = "secretKey";
test("smoke test", async () => {
// payloadを準備する
const payload = {
userId: "569125ee-56f8-4955-937c-70a5fa8ababc",
exp: 1657168882,
};
const result = createJwt(payload, JWT_KEY);
const decodedPayload = jwtDecode<typeof payload>(result);
expect(result).toBeTruthy();
expect(decodedPayload.userId).toBe(payload.userId);
});
呼び出し可能関数を実装してみる
バックエンドで動いているNestJSサーバーに対してGETリクエストを送って受け取ったデータをそのままクライアントに返す感じ。
対象ユーザーのUIDはそのままcontext.auth.uid
を使用する。これをもとにJWT文字列を作成して、ヘッダーに設定した上でリクエストを送信する。
実際に作成する場合は、送られてきたuidのユーザーがFirebase Authenticationに登録されているかを確認したり、バックエンドにも問い合わせたりなどの処理を追加した方が良いかもしれない。
// src/routes/tab-one/activities/getActivities.ts
import type {CallableMethod} from "~/@types/callableMethod";
import {createJwt} from "~/utils/createJwt";
import fetch from "node-fetch";
type Activity = {
id: string;
label: string;
date: string;
value: string;
method: "COUNT" | "TIME";
exp: number;
createdAt: string;
icon: string;
};
// eslint-disable-next-line @typescript-eslint/ban-types
export type RequestData = {};
export type ResponseData = {
formError?: string;
activities?: Activity[];
};
export const getActivities: CallableMethod = async (
data,
context
): Promise<ResponseData> => {
// uidの有無を確認する
if (!context.auth?.uid || context.auth.uid === "") {
const result: ResponseData = {
formError: "uidを取得することができませんでした",
};
return result;
}
const key = "secretKey";
const payload = {
sub: context.auth.uid,
};
const token = createJwt(payload, key);
const response = await fetch(
`http://localhost:3000/users/${
payload.sub
}/activities?method=year&base_date=${new Date().getTime()}`,
{
method: "GET",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
}
);
const latestActivities = await response.json() as Activity[];
return {
activities: latestActivities,
};
};
呼び出し可能関数用のテストを作成してみる
先ほど作成したgetActivities
関数を実際に呼び出してみて、結果が帰ってくるかの簡単なテストを行なってみる。
※先にNestJS側でサーバーを起動したり、必要なデータをシードしておくこと。
// src/routes/tab-one/activities/getActivities.spec.ts
import * as firebaseFunctions from "firebase-functions";
import firebaseFunctionsTest from "firebase-functions-test";
import {getActivities, RequestData} from "./getActivities";
import {WrappedFunction} from "firebase-functions-test/lib/v1";
let wrapped: WrappedFunction<RequestData>;
beforeAll(async () => {
const {wrap} = firebaseFunctionsTest();
// テスト対象の関数をラップする
wrapped = wrap(firebaseFunctions.https.onCall(getActivities));
});
afterAll(() => {
vi.clearAllMocks();
});
test("smoke test", async () => {
expect(1).toBe(1);
const userId = "09ed994e-7ca0-4d9f-a1a5-c7d24ada765a";
// 実行する
const result = await wrapped({}, {auth: {uid: userId}});
expect(result.activities.length).toBe(4);
});
今回は内部でFirebase AuthenticationやFirestoreを使用していていないので、エミュレーターは使用しないので、そのままvitest
を実行するだけで良い。
yarn vitest
もし使う場合は次のコマンドを使用する。yarn test
を実行するために"test": "vitest"
スクリプトをpackage.jsonに追加しておく。
firebase emulators:exec --project project-id --only auth 'yarn build && yarn test'