Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 1x 1x 1x 1x 4x 2x 2x 2x 1x 1x 1x 2x 2x 1x 1x 1x 1x 1x 1x 1x | import admin from "firebase-admin";
import { AuthenticationError } from "../utils/errors.js";
// Firebase Admin SDKを初期化します。
// Cloud Functionsのようなサーバーレス環境では、関数の呼び出しごとにコードが再評価される可能性があります。
// `admin.apps.length === 0` のチェックは、SDKが複数回初期化されるのを防ぐための定石です。
// 重複初期化はエラーを引き起こすため、このガード句が重要になります。
Eif (admin.apps.length === 0) {
admin.initializeApp();
}
const db = admin.firestore();
// トークン用のFirestoreコレクション
const FITBIT_TOKENS_COLLECTION = "fitbit_tokens";
interface FitbitTokens {
accessToken: string;
refreshToken: string;
expiresAt: number;
fitbitUserId: string;
firebaseUids: string[];
}
/**
* フロントエンドから送られてきたFirebase IDトークンを検証し、デコードされた内容を返します。
* これは、リクエスト元が正規のFirebaseユーザーであることを確認するための重要なセキュリティステップです。
* トークンが無効、期限切れ、または不正な形式である場合はエラーをスローし、認証を失敗させます。
*
* @param idToken クライアントから `Authorization: Bearer <ID_TOKEN>` ヘッダーで送信されるJWT。
* @returns 検証済みユーザーのデコードされたトークン情報(uid, emailなどを含む)。
* @throws {AuthenticationError} トークンの検証に失敗した場合。
*/
export async function verifyFirebaseIdToken(
idToken: string,
): Promise<admin.auth.DecodedIdToken> {
if (!idToken) {
throw new AuthenticationError("ID token is required.");
}
try {
const decodedToken = await admin.auth().verifyIdToken(idToken);
return decodedToken;
} catch (error) {
console.error("Error verifying Firebase ID token:", error);
throw new AuthenticationError("Invalid ID token.");
}
}
/**
* 指定されたFirebase UIDに関連付けられたFitbitトークンをFirestoreから取得します。
* データモデルとして、`firebaseUids` フィールドにUIDが含まれているドキュメントを検索します。
* これにより、将来的に複数のFirebaseアカウントが同じFitbitアカウントにリンクするようなシナリオにも対応可能です。
*
* @param firebaseUid 検索キーとなるFirebaseユーザーのUID。
* @returns ユーザーに関連付けられたトークン情報。見つからない場合は `null` を返す。
*/
export async function getTokensFromFirestore(
firebaseUid: string,
): Promise<FitbitTokens | null> {
const querySnapshot = await db
.collection(FITBIT_TOKENS_COLLECTION)
.where("firebaseUids", "array-contains", firebaseUid)
.limit(1)
.get();
if (querySnapshot.empty) {
console.log(`No token found for user ${firebaseUid}`);
return null;
}
return querySnapshot.docs[0].data() as FitbitTokens;
}
/**
* Fitbitのトークン情報をFirestoreに保存または更新します。
*
* データモデルの重要な点:
* - **ドキュメントID**: `fitbitUserId` を使用します。これにより、Fitbitユーザーごとにトークン情報が一意に保たれます。
* - **`firebaseUids`**: FirebaseのUIDを配列 (`arrayUnion`) で管理します。これにより、1つのFitbitアカウントに対して複数のFirebaseアカウント(例:Googleログイン、メールログインなど)を紐付けることが可能になります。
* - **`merge: true`**: このオプションにより、ドキュメント全体を上書きするのではなく、指定されたフィールドのみを更新(または新規作成)します。`firebaseUids` に新しいUIDを追加する際に、既存のUIDを消さずに済みます。
*
* @param firebaseUid 関連付けるFirebaseユーザーのUID。
* @param fitbitUserId 保存するトークンの持ち主であるFitbitユーザーのID。
* @param tokens Fitbit APIから返されたトークンオブジェクト(`access_token`, `refresh_token`, `expires_in` を含む)。
*/
export async function saveTokensToFirestore(
firebaseUid: string,
fitbitUserId: string,
tokens: any,
): Promise<void> {
const expiresAt = new Date().getTime() + tokens.expires_in * 1000;
const tokenData = {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: expiresAt,
fitbitUserId: fitbitUserId,
firebaseUids: admin.firestore.FieldValue.arrayUnion(firebaseUid),
};
await db
.collection(FITBIT_TOKENS_COLLECTION)
.doc(fitbitUserId)
.set(tokenData, { merge: true });
console.log(
`Successfully saved tokens for Firebase user ${firebaseUid} (Fitbit user ${fitbitUserId})`,
);
}
|