Jacques Tellier
Jacques Tellier

Reputation: 1

Firestore missing or insufficient permissions using App Check

I am working on developping a nextjs website for myself and right now I am focusing on data. It's a portfolio website, so no account needed. My current problem is that I can't run my app using firestore with App Check runing.

The error I am facing is this one :

Erreur API Firestore : [Error [FirebaseError]: Missing or insufficient permissions.] {
  code: 'permission-denied',
  customData: undefined,
  toString: [Function (anonymous)]
}
 GET /api/api-handler?endpoint=cinema 500 in 78ms



To begining with a page called cinema.js :

import Nav from '@/components/Nav';
import Head from 'next/head';
import Footer from '@/components/Footer';
import Movie from '@/components/Movie';
import Link from 'next/link';
import React, { useState } from 'react';

export async function getServerSideProps(context) {
  try {
    const protocol = process.env.NODE_ENV === "production" ? "https" : "http";
    const host = context.req.headers.host;
    const response = await fetch(`${protocol}://${host}/api/api-handler?endpoint=cinema`);
    const data = await response.json();

    return {
      props: {
        projects: data,
      },
    };
  } catch (error) {
    console.error("Erreur lors de la récupération des données :", error);
    return {
      props: {
        projects: [],
      },
    };
  }
}

export default function Cinema({ projects }) {
  const [activeFilm, setActiveFilm] = useState(null);
  const [activeItem, setActiveItem] = useState(null);

  return (
    <div className="h-screen text-txt-default font-DM-Sans bg-bg-default bg-right-gradient p-6 flex flex-col justify-between select-none">
      <Head>
        <title>Cinéma</title>
      </Head>

      {/* Barre de navigation */}
      <div className="mb-6">
        <div className="flex justify-between projects-center">
          <div className="flex">
            <Link href="/" className="flex space-x-2 select-none" draggable="false">
              <h6 className="text-txt-default">Jacques</h6>
              <h6 className="text-txt-second">Tellier</h6>
            </Link>
          </div>
          <Nav />
        </div>
        {/* Titre principal */}
        <h2 className="text-accent select-none">CINÉMA</h2>
      </div>

      {/* Contenu principal */}
      <div className="flex flex-col 2xl:flex-row gap-8 2xl:my-20 mx-auto h-[75vh]">
        {/* Bloc des détails */}
        <div className="flex flex-col p-4 2xl:p-6 w-full 2xl:w-80 rounded-lg shadow-muted bg-bg-second order-first 2xl:order-none">
          {activeItem ? (
            <>
              <h4 className="text-txt-default mb-2 2xl:mb-4 text-base 2xl:text-lg">{activeItem.title}</h4>
              <p className="text-txt-muted text-sm 2xl:text-base">{activeItem.description}</p>
              <div className="mt-4 text-xs 2xl:text-sm">
                <p>
                  Année : <span className="text-txt-second">{activeItem.date}</span>
                </p>
                <p>
                  Genre :{" "}
                  <span className="text-txt-second">
                    {activeItem.genre.join(", ")}
                  </span>
                </p>
                <p>
                  Type :{" "}
                  <span className="text-txt-second">
                    {activeItem.type}
                  </span>
                </p>
                <p>
                  Réalisateur(s) : <span className="text-txt-second">{activeItem.director.join(", ")}</span>
                </p>
                <p>
                  Producteur(s) : <span className="text-txt-second">{activeItem.producer.join(", ")}</span>
                </p>
                <p>
                  Distribution :{" "}
                  <span className="text-txt-second">
                    {activeItem.distribution.join(", ")}
                  </span>
                </p>
                <br />
                <p>
                  Mon rôle :{" "}
                  <strong>
                    <span className="text-accent">{activeItem.role}</span>
                  </strong>
                </p>
              </div>
              <button
                onClick={() => setActiveFilm(activeItem)}
                className="mt-4 2xl:mt-10 px-4 2xl:px-6 py-2 2xl:py-3 bg-bg-inverse text-txt-inverse font-bold rounded-full hover:bg-bg-second hover:text-txt-default hover:shadow-inverse transition duration-100 select-none"
                draggable="false"
              >
                Voir +
              </button>
            </>
          ) : (
            <p className="text-txt-muted text-sm 2xl:text-base">Sélectionnez un élément pour afficher les détails.</p>
          )}
        </div>

        {/* Grille des affiches */}
        <div className="grid grid-cols-3 2xl:grid-cols-4 overflow-y-auto scrollbar-hide 2xl:gap-4 order-last 2xl:order-none">
          {projects.length > 0 ? (
            projects.map((item) => (
              <div
                key={item.id}
                className="flex flex-col rounded-lg p-4 hover:scale-105 transition-transform cursor-pointer"
                onClick={() => setActiveItem(item)}
              >
                <img
                  src={item.poster}
                  alt={item.title}
                  className="w-full h-32 2xl:h-64 object-cover rounded-md hover:shadow-inverse"
                />
                <div className="text-center mt-2 2xl:mt-4">
                  <h6 className="font-bold text-sm 2xl:text-base line-clamp-1">{item.title}</h6>
                  <p className="text-xs 2xl:text-sm text-txt-muted">{item.date}</p>
                </div>
              </div>
            ))
          ) : (
            <div className="col-span-full text-center text-lg text-txt-muted">
              Aucun projet pour le moment.
            </div>
          )}
        </div>
      </div>

      {/* Affichage de Movie en position absolue */}
      {activeFilm && (
        <Movie
          film={activeFilm}
          onClose={() => setActiveFilm(null)}
        />
      )}

      {/* Footer */}
      <Footer />
    </div>
  );
}

The API calls are beeing recieved here, in api-handler.js file :

import { collection, getDocs } from "firebase/firestore"; // Ajout de l'import manquant
import { db } from "@/config/firebase-config";

export default async function handler(req, res) {
  const { endpoint } = req.query; // Récupère le type de données demandé (ex : "cinema")

  if (req.method === "GET") {
    try {
      console.log("Requête reçue pour endpoint :", endpoint);

      const dataCollection = collection(db, endpoint); // Référencer la collection Firestore
      console.log("\n")
      console.log("Instance Firestore:", dataCollection._firestore);
      console.log("Chemin de la collection :", dataCollection._path.segments);
      console.log("Convertisseur :", dataCollection._converter);
      console.log("\n")
      const snapshot = await getDocs(dataCollection); // Récupérer les documents
      const data = snapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));

      console.log("Données récupérées :", data);
      res.status(200).json(data); // Retourne les données récupérées au client
    } catch (error) {
      console.error("Erreur API Firestore :", error);
      res.status(500).json({ error: "Erreur lors de la récupération des données." });
    }
  } else {
    res.setHeader("Allow", ["GET"]);
    res.status(405).end(`Méthode ${req.method} non autorisée`);
  }
}

And we are using this firebase-config.js file :

import { initializeApp, getApps, getApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
import { initializeAppCheck, ReCaptchaV3Provider } from "firebase/app-check";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL, // Ajout de la configuration manquante
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const app = !getApps().length ? initializeApp(firebaseConfig) : getApp();
const db = getFirestore(app);
const storage = getStorage(app);

if (typeof window !== "undefined") {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;

  initializeAppCheck(app, {
    provider: new ReCaptchaV3Provider(process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY),
    isTokenAutoRefreshEnabled: true, // Renouvelle automatiquement le jeton
  });
}

export { app, db, storage };

Here are the rules I am using on Firestore :

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if true;
      allow write: if false;
    }
  }
}

All is working when I turn off App check on the Firestore feature, I get my data, there is no errors, but ! I don't feel comfy without the app check on my app...

This is an example of one page with data included from firestore :

With App Check : https://i.sstatic.net/gYLvtztI.png

Without App Check : https://i.sstatic.net/pByvMAHf.png

App Check status when enforced : https://i.sstatic.net/p4VD3zfg.png

Upvotes: 0

Views: 67

Answers (0)

Related Questions