Как исправить FirebaseError: отсутствуют или недостаточны разрешения в React Hook?

Я использую комбинацию React Hook, Next.js и Firebase Auth для создания записи пользовательской монеты в Firestore. Это означает, что как только новый пользователь зарегистрируется, для него в Firestore должен быть автоматически создан новый документ с пометкой uid вместе с полями coins и createdAt.

Однако, когда я вхожу в систему в первый раз, я получаю сообщение об ошибке, в котором говорится:

гидратация-ошибка-информация.js: 27 Ошибка в createUserCoinRecord: FirebaseError: Отсутствуют или недостаточны разрешения».

Однако, если я выйду из системы и войду снова, ошибка больше не возникает.

При проверке базы данных Firebase я заметил, что uid для пользователя не создается после первого входа в систему, а создается только после второго входа.

Вот код React Hook:

export function useCoinRecord() {
  const { data: session } = useSession();
  const userEmailRef = useRef(null);
  const [created, setCreated] = useState(false);

  async function createUserCoinRecord(uid) {
    await setDoc(doc(db, "users", uid), {
      coins: 100000,
      createdAt: serverTimestamp(),
    });
  }

  useEffect(() => {
    async function createCoinRecordIfNeeded() {
      if (session) {
        if (userEmailRef.current !== session.user.email) {
          userEmailRef.current = session.user.email;
          try {
            await createUserCoinRecord(session.user.email);
            setCreated(true);
          } catch (error) {
            console.error("Error in createUserCoinRecord:", error);
          }
        }
      } else {
        userEmailRef.current = null;
      }
    }

    createCoinRecordIfNeeded();
  }, [session]);

  return { created };
}

И это мои правила Firestore:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read, write: if request.auth != null && request.auth.uid == uid;
    }
    match /users/{uid}/{document=**} {
      allow read, write: if request.auth != null && request.auth.uid == uid;
    }
  }
}

Кнопка входа:

import { signIn } from "next-auth/react";
//...
      <button onClick = {() => signIn("google")}>
        Sign In
      </button>

Войдите с помощью следующей аутентификации:

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
  ],
};

export default NextAuth(authOptions);

Затем используйте useEffect для входа в Firebase:

import { useSession } from "next-auth/react";
import { auth, db } from "../firebase/firebase";
//...

  const { data: session } = useSession();
  const { coinBalance, setCoinBalance } = useCoinBalanceContext();
  const [readyToFetchBalance, setReadyToFetchBalance] = useState(false);

  useEffect(() => {
    if (session) {
      signInWithFirebase(session).then((uid) => {
        if (uid) {
          setReadyToFetchBalance(true);
        }
      });
    }
  }, [session]);

  const signInWithFirebase = async (session) => {
    const response = await fetch("/api/firebase/create-custom-token", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ user: session.user }),
    });

    const customToken = await response.json();

    return signInWithCustomToken(auth, customToken.token)
      .then((userCredential) => {
        return userCredential.user.uid;
      })
      .catch((error) => {
        console.error("Firebase sign-in error:", error);
      });
  };

  const { created } = useCoinRecord();

  useEffect(() => {
    if (readyToFetchBalance && created && session?.user?.email) {
      (async () => {
        const balance = await getCoinBalance(session.user.email);
        balance && setCoinBalance(balance);
      })();
    }
  }, [session, readyToFetchBalance, created]);

/api/firebase/create-custom-token

import { adminAuth } from "@/firebase/firebaseAdmin";

export default async function handler(req, res) {
  if (req.method !== "POST") {
    res.status(405).json({ message: "Method not allowed" });
    return;
  }

  const { user } = req.body;

  const uid = user.email;

  try {
    const customToken = await adminAuth.createCustomToken(uid);

    res.status(200).json({ token: customToken });
  } catch (error) {
    console.error("Error creating custom token:", error);
    res.status(500).json({ message: "Error creating custom token" });
  }
}

firebase.js

import { getApp, getApps, initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
apiKey: process.env.FIREBASE_APIKEY,
authDomain: process.env.FIREBASE_AUTHDOMAIN,
databaseURL: process.env.FIREBASE_DATABASEURL,
projectId: process.env.PROJECTID,
storageBucket: process.env.FIREBASE_STORAGEBUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGINGSENDERID,
appId: process.env.FIREBASE_APPID,
measurementId: process.env.FIREBASE_MEASUREMENTID,
};

const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
export { db, auth };

firebaseAdmin.js

import admin from "firebase-admin";

const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY);
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
  });
}

const adminDb = admin.firestore();
const adminAuth = admin.auth();

export { adminDb, adminAuth };

Моя база данных устроена так:

/users/[email protected]/coins

где [email protected] это uid.

Что может быть причиной FirebaseError и как я могу это исправить?

Может этот ресурс поможет.

Alex Mamo 19.04.2023 11:57

request.auth автоматически заполняется, когда вы подписываете пользователя с аутентификацией Firebase, вызывая одну из функций signInWith..., но ни один из кодов, которыми вы поделились, этого не делает.

Frank van Puffelen 19.04.2023 15:26

@FrankvanPuffelen Спасибо, Фрэнк. Я добавил больше файлов. И спасибо за ссылку, Алекс.

Tui 20.04.2023 00:19

@FrankvanPuffelen Я добавил к этому больше контекста: когда я вхожу в систему в первый раз, я сталкиваюсь с ошибкой. Однако, если я выйду из системы и войду снова, ошибка больше не возникает. При проверке базы данных Firebase я заметил, что uid для пользователя не создается после первого входа в систему, а создается только после второго входа.

Tui 20.04.2023 01:20

Я нашел проблему и написал решение. Спасибо ребята.

Tui 20.04.2023 02:24
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
5
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я понял, что существует гонка между двумя крюками, которые работают одновременно.

Проблема в том, что useCoinRecord() пытается создать новый документ до того, как пользователь будет аутентифицирован. Чтобы исправить это, я удалил useCoinRecord() и изменил хук useEffect(), чтобы включить необходимую логику для создания пользовательской записи о монетах, прежде чем устанавливать флаг ReadyToFetchBalance в значение true.

Обновленный код выглядит так:

useEffect(() => {
  if (session) {
    signInWithFirebase(session).then(async (uid) => {
      if (uid) {
        await createUserCoinRecord(uid);
        setReadyToFetchBalance(true);
      }
    });
  }
}, [session]);

Это должно решить проблему состояния гонки и гарантировать, что документ будет создан только после аутентификации пользователя.

Другие вопросы по теме