Home > Software design >  Adding Firestore rule "&& request.auth.uid == userId;" results in"Error in snapshot l
Adding Firestore rule "&& request.auth.uid == userId;" results in"Error in snapshot l

Time:03-14

I'm building an app and setting the firestore rules to only allow users CRUD access to their data in a simple, single-layer DB.

Firestore Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /todos/{userId} {
      allow create, read, update, delete: if request.auth != null && request.auth.uid == userId;
    }
  }
}

If I only use the if statement if request.auth != null the user is able to see all data in the DB.

when I add&& request.auth.uid == userId I get the console error, "react_devtools_backend.js:4061 Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions." The user can't see any data, including their own.

I've read the docs but am missing something-- I think either in my query, snapshot, or both?

Here's my code:

Init firebase (firebase.js)

// init Firebase
export const app = initializeApp(firebaseConfig);

//init services
export const db = getFirestore(app);
export const auth = getAuth();

and then my query and snapshot (app.js)

import React, { useState, useEffect } from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import InputGroup from "react-bootstrap/InputGroup";
import "./App.css";
import Todo from "./components/Todo";
import { db } from "./firebase";
import {
  collection,
  query,
  orderBy,
  onSnapshot,
  addDoc,
  serverTimestamp,
} from "firebase/firestore";

//collection ref, order by functionality

const q = query(
  collection(db, "todos"),
  orderBy("timestamp", "desc")
);

//firebase functionality
function App({ user }) {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState("");
  useEffect(() => {
    onSnapshot(q, (snapshot) => {
      setTodos(
        snapshot.docs.map((doc) => ({
          id: doc.id,
          item: doc.data(),
        }))
      );
    });
  }, [input]);
  const addTodo = (e) => {
    e.preventDefault();
    addDoc(collection(db, "todos"), {
      todo: input,
      timestamp: serverTimestamp(),
      userId: user.uid,
    });
    setInput("");
  };

I'd appreciate any guidance/tips. I've been working through this for a couple days now. Thanks!

CodePudding user response:

Your security rules allow the user to read exactly one document, at the path /todos/$uid. Your code however tries to read the entire todos collection, which the rules don't allow. That explains the error you get.

Keep in mind that rules are not filters, but instead merely enforce that you're not trying to read (or write) more data than is allowed. If you want to read the current user's document, you should wait until the current user is loaded (using an auth state listener) and then only load that user's document.

CodePudding user response:

With Frank's guidance I was able to find a solution. Thanks Frank!

These are my updated rules

rules_version = '2';
service cloud.firestore {
   match /{todos=**} {
      allow read, update, delete: if request.auth != null && resource.data.userId == request.auth.uid;
      allow create: if request.auth !=null;
      }
  }

My updated query and snapshot

import React, { useState, useEffect } from "react";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import InputGroup from "react-bootstrap/InputGroup";
import "./App.css";
import Todo from "./components/Todo";

import {
  collection,
  query,
  orderBy,
  onSnapshot,
  addDoc,
  serverTimestamp,
  where,
} from "firebase/firestore";
import { db } from "./firebase";

//firebase functionality
function App({ user }) {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState("");
  const [lastChange, setLastChange] = useState("");

  //executing the query
  useEffect(() => {
    //console.log(user);
    if (user == null) return;

    var q = query(
      collection(db, "todos"),
      orderBy("timestamp", "desc"),
      where("userId", "==", user.uid)
    );

    onSnapshot(q, (snapshot) => {
      setTodos(
        snapshot.docs.map((doc) => ({
          id: doc.id,
          item: doc.data(),
        }))
      );
    });
  }, [lastChange, user]);

  //add a new task
  const addTodo = (e) => {
    e.preventDefault();
    addDoc(collection(db, "todos"), {
      todo: input,
      timestamp: serverTimestamp(),
      userId: user.uid,
    });
    setInput("");
    setLastChange(serverTimestamp());
  };
  • Related