0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

firebaseのdatabaseのアクセス数制限

Posted at

悪意ある攻撃対策

ある日突然データが改ざんされた??

運用していたテニスサークルのデータが改ざんされていました。
色々調べるとセキュリティが甘かった。
セキュリティは色々、触ったけど、firebaseは従量課金のため膨大な読み込みがなされた場合、費用が膨大になる可能性があります。
最悪の事態を回避するため、一日のアクセス数の上限を設定することとしました。
以下は忘備録です。

functionsでの設定

アクセス数をdatabase のsystemコレクションのglobalAccessといドキュメントに保存して管理を行います。
コレクションとドキュメントは自動で生成されないので、手動で設定します。
ドキュメントには、lastResetTimeというTimestampのものと、requestCountというNumberのプロパティが必要です。
この仕組みでは、ドキュメントにアクセスされる度に、記録されるため結果的にアクセス数は二倍となります。

index.js
const cors = require("cors")({ origin: true });
const db = admin.firestore();

exports.limitFirestoreAccess = functions.https.onRequest(async (req, res) => {
  cors(req, res, async () => {
    try {
      const now = new Date();

      const globalRef = db.collection("system").doc("globalAccess");
      const doc = await globalRef.get();

      let requestCount = 0;
      let lastResetTime = now;

      if (doc.exists) {
        const data = doc.data();
        requestCount = data.requestCount || 0;
        lastResetTime = data.lastResetTime;

        if (lastResetTime) {
          const lastResetTimeDate = lastResetTime.toDate();
          const elapsedTime = now.getTime() - lastResetTimeDate.getTime();
          const oneHour = 60 * 60 * 1000;

          if (elapsedTime >= oneHour) {
            console.log("リセット条件を満たしたため、requestCount をリセット");
            requestCount = 0;
            lastResetTime = now;
          }
        }
      }

      if (requestCount >= 3000) {
        return res
          .status(429)
          .json({ error: "⚠️ Firestore のアクセスが制限されています!" });
      }

      requestCount += 1;
      console.log(`Updating access count: ${requestCount}`);

      await globalRef.set(
        {
          requestCount: requestCount,
          lastResetTime: now,
        },
        { merge: true }
      );

      res.status(200).json({ message: "✅ アクセス許可" });
    } catch (error) {
      console.error("Error updating access count:", error);
      res.status(500).json({ error: "内部サーバーエラー" });
    }
  });
});

上記はfunctionsにデプロイします。

##チェックする関数を作成

CheckAccessLimit.js
export const CheckAccessLimit = async () => {
  try {
    const response = await fetch(
      "https://us-central1-circle-base-a3abd.cloudfunctions.net/limitFirestoreAccess",
      {
        method: "GET",
        mode: "cors",
        headers: {
          "Content-Type": "application/json",
        },
      }
    );

    if (!response.ok) {
      throw new Error("Network response was not ok");
    }

    const contentType = response.headers.get("content-type");
    let data;

    if (contentType && contentType.includes("application/json")) {
      data = await response.json();
    } else {
      data = await response.text();
    }

    if (typeof data === "string") {
      console.log("Response is a string:", data);
    } else {
      console.log("Received JSON data:", data);
    }

    if (data.error) {
      console.error("members", data.error);
      return false;
    }

    return true;
  } catch (error) {
    console.error("アクセス制限の確認中にエラーが発生しました:", error);
    return false;
  }
};

##呼び出し
この例では、fetchEventData.jsでeventコレクションのデータを呼び出しています。

fetchEventData.js
// src/utils/fetchEventData.js
import { parseISO } from "date-fns";
import { collection, onSnapshot, query, where } from "firebase/firestore";
import { db } from "../firebase"; // Firebaseの設定がされているファイルからdbをインポート
import { convertDurationToMinutes } from "./convertDurationToMinutes";
import { CheckAccessLimit } from "../utils/CheckAccessLimit";

CheckAccessLimit();

export const subscribeToEventData = async (callback) => {
  try {
    const eventCollection = collection(db, "events");
    const canAccess = await CheckAccessLimit();
    if (!canAccess) {
      console.log("アクセス制限がかかっているため、データ取得を中止します。");
      alert(
        "アクセス数が上限のためアクセスができません。しばらくしてからアクセスしてください。"
      );
      return;
    }
    const unsubscribe = onSnapshot(eventCollection, (snapshot) => {
      const eventList = snapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(), // すべてのデータを取得
      }));

      if (eventList.length > 0 && eventList[0].jsonData) {
        const parseEventList = JSON.parse(eventList[0].jsonData);
        const eventId = eventList[0].id;
        callback(parseEventList, eventId); // idもコールバックに渡す
      } else {
        console.warn("No valid jsonData found in members collection.");
      }
    });

    return unsubscribe; // クリーンアップ用
  } catch (error) {
    console.error("Error subscribing to event data:", error);
    return () => {}; // 何も行わないクリーンアップ関数を返す
  }
};
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?