Why is the registered user not Authenticated in Firebase? Why can't the user add products to the database Firebase?

Viewed 605

What should I add to my project so that the owner who is authenticated can add products or modify them?

What should I do so that the registered user can add products to the catalog from the Administrator Panel?

I have an application that I create with npx create-react-app my-app The Web App is an Administration panel for the products of a restaurant, store, or street vendor.

In it, the business owner adds products to the Firebase database, which can later be viewed from a Mobile App through this Administrator Panel.

To increase my knowledge, I have followed projects and tutorials to add the required registration of the user to the App (Administrator Panel)

Finally, I managed to create a small user registration application with firebase, you can see it in this GitHub repository: https://github.com/miguelitolaparra/react-firebase-email-login

With the same components as in the react-firebase-email-login App I have created a registration system in the Administrator Panel and everything works, you can see in the following link that I uploaded the App to a server:

http://restaurantcliente.webapplicationdeveloper.es/

This should be from where the business owner manages their products, adding or removing from the catalog.

To prevent anyone from registering and modifying the owner's catalog, remove the SignUp part and register the user from the Firebase database.

Change the Firebase Rules to be safe as follows:

service cloud.firestore {
  match / databases / {database} / documents {
    match / {document = **} {
      allow read: if true
      allow write: if request.auth.uid == request.data.author_uid
    }
  }
}

The problem has occurred when I try to add new products from the Administrator Panel, since the products are not added to the database. When I change the rules, so that anyone can read and write, it works and the administrator can add products to the database from the Administrator Panel

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

I've seen several examples and tutorials using Private Routes and other components, but I always run into problems adding those systems to my project, due to the new changes to react-router-dom and react-router, which have been removed elements like <Switch>, createHistory, etc.

I don't know how to make my project secure and when the user is registered, I can add items to the catalog from the Administrator panel, without adding them manually from the database.

I don't know what else to do, after trying with examples that I found on GitHub and not achieving the goal. I cannot add all the examples, as I have tried at least 30 different projects. What should I add to my project so the owner can add products or modify them?

What should I do so that the registered user can add products to the catalog from the Administrator Panel?

EDIT to add more code to the question

I hope this can help me get it to work. You can try the example in the following link:

http://restaurantcliente.webapplicationdeveloper.es

Administrator:

restaurantcliente@webapplicationdeveloper.es

Password: Restaurant1920

The biggest problem is that the Web application, I need to wrap it entirely in the authentication mode, so that all its elements and functionalities are available when I edit the rules "as Safe" But this I don't know how to do it

The database consists of two collections: Orders and Products The Orders collection is created from the Mobile App, when a customer places an order from the mobile application. The Products collection is created by the restaurant owner from the Administrator Panel.

This is the file: NuevoPlato.js

import React, { useContext, useState } from 'react'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { FirebaseContext } from '../../firebase'
import { useNavigate } from 'react-router-dom'
import FileUploader from 'react-firebase-file-uploader'

const NuevoPlato = () => {

  // state para las imagenes
  const [subiendo, guardarSubiendo] = useState(false)
  const [progreso, guardarProgreso] = useState(0)
  const [urlimagen, guardarUrlimagen] = useState('')

  // Context con las operaciones de firebase
  const { firebase } = useContext(FirebaseContext)

  //console.log(firebase)

  // Hook para redireccionar
  const navigate = useNavigate()

  // validación y leer los datos del formulario
  const formik = useFormik({
    initialValues: {
      nombre: '',
      precio: '',
      categoria: '',
      imagen: '',
      descripcion: '',
    },
    validationSchema: Yup.object({

      nombre: Yup.string()
        .min(3, 'Los Platillos deben tener al menos 3 caracteres')
        .required('El Nombre del platillo es obligatorio'),
      precio: Yup.number()
        .min(1, 'Debes agregar un número')
        .required('El Precio es obligatorio'),
      categoria: Yup.string()
        .required('La categoría es obligatoria'),
      descripcion: Yup.string()
        .min(10, 'La descripción debe ser más larga')
        .required('La descripción es obligatoria')

    }),
    onSubmit: plato => {
      try {
        plato.existencia = true
        plato.imagen = urlimagen

        firebase.db.collection('productos').add(plato)

        // Redireccionar
        navigate('/menu')
      } catch (error) {
        console.log(error)
      }
    }
  })
  // Todo sobre las imagenes
  const handleUploadStart = () => {
    guardarProgreso(0)
    guardarSubiendo(true)
  }
  const handleUploadError = error => {
    guardarSubiendo(false)
    console.log(error)
  }
  const handleUploadSuccess = async nombre => {
    guardarProgreso(100)
    guardarSubiendo(false)

    // Almacenar la URL de destino
    const url = await firebase
      .storage
      .ref("productos")
      .child(nombre)
      .getDownloadURL()

    console.log(url)
    guardarUrlimagen(url)
  }
  const handleProgress = progreso => {
    guardarProgreso(progreso)

    console.log(progreso)
  }


  return (
    <>
      <h1 className="text-3xl font-light mb-4">Add new plate</h1>

      <div className="flex justify-center mt-10">
        <div className="w-full max-w-3xl">
          <form
            onSubmit={formik.handleSubmit}
          >
            
// THIS IS THE FORM WITH THE DATA                <input
              type="submit"
              className="bg-gray-800 hover:bg-gray-900 w-full mt-5 p-2 text-white uppercase font-bold"
              value="Agregar Plato"
            />
          </form>
        </div>
      </div>
    </>
  )

}

export default NuevoPlato

From the Administrator panel, the "delivery time" is also managed, the time it will take for the order to be finished and which is defined by the owner of the restaurant from the panel.

But I cannot modify this data either so that it is displayed in the Mobile App, if I edit the "As Safe" rules.

File setting delivery time Orden.js

import React, { useState, useContext} from 'react'
import { FirebaseContext } from '../../firebase'

const Orden = ({ orden }) => {

  const [  tiempoentrega, guardarTiempoEntrega ]= useState(0)

// Context de Firebase
const { firebase } = useContext(FirebaseContext)

// define el tiempo de entrega del pedido en tiempo real
const definirTiempo = id => {
  try {
    firebase.db.collection('ordenes')
        .doc(id)
        .update({
          tiempoentrega
        })
  } catch (error) {
    console.log(error)
  }
}

// Marcar que el pedido esta completado
const completarOrden = id => {
   try {
     firebase.db.collection('ordenes')
           .doc(id)
           .update({
             completado: true
           })
   } catch (error) {
     console.lor(error)
   }
}

  return (
    <div className="sm:w-1/2 lg:w-1/3 px-2 mb-4">
      <div className="p-3 shadow-md bg-white">
        <h1 className="text-yellow-600 text-lg font-bold"> {orden.id} </h1>
        {orden.orden.map(platos => (
          <p className="text-gray-600"> {platos.cantidad} {platos.nombre} </p>
        ))}

        <p className="text-gray-700 font-bold">Total a Pay: {orden.total}$</p>

        {orden.tiempoentrega === 0 && (
          <div className="mb-4">
            <label className="block text-gray-700 text-sm font-bold mb-2">
            Delivery Time
            </label>

            <input
              type="number"
              className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
              min="1"
              max="30"
              placeholder="Pon El Tiempo"
              value={tiempoentrega}
              onChange={ e => guardarTiempoEntrega( parseInt(e.target.value))}
            />

            <button
            onClick={ () => definirTiempo(orden.id) }
              type="submit"
              className="bg-gray-500 hover:bg-gray-700 w-full mt-5 p-2 text-white uppercase font-bold"
            >
              Define Time
            </button>
          </div>
        )}

        {orden.tiempoentrega > 0 && (
          <p className="text-gray-700">Delivery Time:
          <span className="font-bold"> {orden.tiempoentrega} Minutes </span>
           </p>
        )}

        { !orden.completado && orden.tiempoentrega > 0 &&(
          <button
          type="button"
          className="bg-blue-700 hover:bg-blue-400 w-full mt-5 p-2 text-white uppercase font-bold"
          onClick={ () => completarOrden(orden.id)}
          >
            Declare Done
          </button>
        )}
      </div>
    </div>
  )
}

export default Orden

I show some of the files

File App.js

import React, { useState, useEffect } from "react"
import { Routes, Route } from "react-router"

import firebase, { FirebaseContext } from "./firebase"
//import { auth } from 'firebase'
import firebaseObj from "./firebase"
import Ordenes from "./components/paginas/Ordenes"
import Menu from "./components/paginas/Menu"
import NuevoPlato from "./components/paginas/NuevoPlato"
import Sidebar from "./components/ui/Sidebar"
import Signin from "./components/Signin"

const auth = firebaseObj.auth

function App() {
  const [user, setUser] = useState(null)
  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((userAuth) => {
      const user = {
        uid: userAuth?.uid,
        email: userAuth?.email,
      }
      if (userAuth) {
        console.log(userAuth)
        setUser(user)
      } else {
        setUser(null)
      }
    })
    return unsubscribe;
  }, [])

  if (!user) {
    return (
      <div className="md:flex min-h-screen">
        <div className="md:w-2/5 xl:w-4/5 p-6">
          <Signin />
        </div>
      </div>
    )
  } else {
    return (
      <FirebaseContext.Provider
        value={{
          firebase,
        }}
      >
        <div className="md:flex min-h-screen">
          <Sidebar />
          <div className="md:w-2/5 xl:w-4/5 p-6">
            <Routes>

              <Route path="/" element={<Ordenes />} />
              <Route path="/menu" element={<Menu />} />
              <Route path="/nuevo-plato" element={<NuevoPlato />} />
            </Routes>
          </div>
        </div>

      </FirebaseContext.Provider>
    );
  }
}

export default App

File Signin.js

import React, { useRef } from 'react'
import firebaseObj from '../firebase/firebase'

const auth = firebaseObj.auth

const Signin = () => {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const signUp = e => {
    e.preventDefault();
    auth.createUserWithEmailAndPassword(
      emailRef.current.value,
      passwordRef.current.value
    ).then(user => {
      console.log(user)
    }).catch(err => {
      console.log(err)
    })
  }
  const signIn = e => {
    e.preventDefault();
    auth.signInWithEmailAndPassword(
      emailRef.current.value,
      passwordRef.current.value
    ).then(user => {
      console.log(user)
    }).catch(err => {
      console.log(err)
    })
  }
  return (
    <div className="flex justify-center mt-10">
      <div className="w-full max-w-3xl">
        <h1 className="my-8 text-yellow-600 text-lg font-bold text-center text-7xl"> My humble restaurant </h1>
        <div className="mb-4">
          <form action="">
            <h1 className="text-center m-3">Start the Work Session</h1>
            <div className="mb-4">
              <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="nombre">Email</label>
              <input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                ref={emailRef} type="email" placeholder="email" />
            </div>
            <div className="mb-4">
              <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="nombre">Password</label>
              <input className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                ref={passwordRef} type="password" placeholder="password" />
            </div>
            <button
              type="button"
              className="bg-blue-700 hover:bg-blue-400 w-full mt-5 p-2 text-white uppercase font-bold"
              onClick={signIn}
            >
              Log In
            </button>
            <div className="mb-4 mt-8 ">
              <h2 className="block text-gray-700 text-sm font-bold mb-2 text-3xl">Enter the administration panel of your restaurant and start preparing dishes and orders</h2>
            </div>
          </form>
        </div>
      </div>
    </div>
  )
}

export default Signin

File package.json

{
      "name": "restaurantcliente",
      "version": "0.1.0",
      "private": true,
      "homepage": "/build", 
      "dependencies": {
        "@testing-library/jest-dom": "^5.11.4",
        "@testing-library/react": "^11.1.0",
        "@testing-library/user-event": "^12.1.10",
        "firebase": "^7.19.0",
        "formik": "^2.2.9",
        "react": "^17.0.2",
        "react-dom": "^17.0.2",
        "react-firebase-file-uploader": "^2.4.4",
        "react-router-dom": "^6.0.0-beta.0",
        "react-scripts": "^4.0.3",
        "web-vitals": "^1.0.1",
        "yup": "^0.32.9"
      },
      "scripts": {
        "start": "npm run watch:css && react-scripts start",
        "build": "npm run build:css && react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject",
        "build:css": "postcss src/css/tailwind.css -o src/css/main.css",
        "watch:css": "postcss src/css/tailwind.css -o src/css/main.css"
      },
      "eslintConfig": {
        "extends": [
          "react-app",
          "react-app/jest"
        ]
      },
      "browserslist": {
        "production": [
          ">0.2%",
          "not dead",
          "not op_mini all"
        ],
        "development": [
          "last 1 chrome version",
          "last 1 firefox version",
          "last 1 safari version"
        ]
      },
      "devDependencies": {
        "autoprefixer": "^10.3.1",
        "history": "^5.0.0",
        "postcss-cli": "^8.3.1",
        "react-router": "^6.0.0-beta.0",
        "tailwindcss": "^2.2.7"
      }
    }
2 Answers

The easiest way would be to go to firebase console, copy the uid of your admin (let's say that the uid of something like XwKD2Mnde8QWqHqN1Q8UfDZe0bF3) . Than your rule should be :

service cloud.firestore {
  match /databases/{database}/documents {
        match /products/{document = **} {
        allow read: if true
        allow write: if request.auth.uid == 'XwKD2Mnde8QWqHqN1Q8UfDZe0bF3'
    }
    match /orders/{document = **} {
      allow read, write: if true
    }
  }
} 

(replace XwKD2Mnde8QWqHqN1Q8UfDZe0bF3 with your admin uid).

You can find the userId here:User UID example

This is the easiest way, but only works with a single predefined value (you also have option to add more than 1 admin account using || so somethink like allow write: if request.auth.uid == 'XwKD2Mnde8QWqHqN1Q8UfDZe0bF3' || request.auth.uid == 'NzGq8...') but this is terrible to maintain if your there are more admins or they might change.

The cleanest way (and easiest to maintain if your users can change) would be to use Custom Claims. But if I understood right what you need (and your experience with firebase) I personally think the first solution might be a better fit for your case.

Or the last option (if you are 100% sure that no user without admin access will ever be present), you can just replace if request.auth.uid == 'XwKD2Mnde8QWqHqN1Q8UfDZe0bF3' with if request.auth.uid != null (But I don't advise this one since if you add any user account at any point and forget to update the rulles, a simple user can edit your product prices for example)

Firebase rules playground: firebase rules playground

First and foremost, as I can see, your system needs multiple roles for users are admin (who will use Admin Panel) and customer (who will use the mobile app). You need a way to distinguish these two roles. You need to have a list of users stored somewhere, maybe Firebase's real-time database or Firestore. After that, you need to add one more field to each user like role which is admin or user. You need to understand Firebase Authentication is a different service and it has nothing related to your real database, so you need to store your users, in other words, sync your users from Firebase Authentication to your database (Firestore/Realtime database), like this question.

The next step is in your rules, you need to change the condition into "allow write if the current user is admin or the current user is the author". I think this is not so hard to write, depends on how you set the above field and where you store your users.

Your question about everything in Admin Panel should be "secured", in other words, people need to authenticate to use the Admin panel right? Everything comes from this method auth.onAuthStateChanged. The idea is, whenever user is null, just redirect to your login page. This is how the "protected route" works. You need to implement it by yourself, it's simple by using the Redirect component from react-router-dom.

As I can see, you like to understand the reason why rather than the code itself, it's good. And I don't see I need to provide any code because you did it well.

Related