Reputation: 1
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