使用Firebase和Next 13进行授权

hvvq6cgz  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(73)

我尝试做一个简单的授权控制,其中“管理员”用户可以访问所有页面,而“经理”用户只能访问主页和建议页面。
然而,我没想到使用next和firebase创建这样的东西会如此困难,我已经尝试了一个多星期,我读了很多关于customClaims的文章,但我无法根据我的需要实现它。
My UserDialogCreate.tsx:

//... my imports

interface UserCreate {
  name: string;
  email: string;
  password: string;
  role: string;
}

const createUserSchema = Yup.object({
  email: Yup.string().required("Email não pode ficar em branco"),
  password: Yup.string().required("Senha não pode ficar em branco"),
});

export function UserDialogCreate() {

  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [confirmPassword, setConfirmPassword] = useState('')
  const [passwordsMatch, setPasswordsMatch] = useState(true);
  const [role, setRole] = useState("manager");
  const [isNewUserModalOpen, setIsNewUserModalOpen] = useState(false);

  function handleOpenNewUserModal() {
    setIsNewUserModalOpen(true);
  }

  async function handleCreateNewUser(e: FormEvent) {
    e.preventDefault();
    if (password === confirmPassword) {
      setPasswordsMatch(true);
      await createUserSchema
      .validate({
        email,
        password,
        confirmPassword,
      })
      .then(() => {
        createUser({
          name,
          email,
          password,
          role: role.toLowerCase(),
        });
        setName("");
        setEmail("");
        setPassword("")
        setConfirmPassword("")
        setRole("manager");
      })
      .catch((error) => {
        toast.error(error.message);
      });

    } else {
      setPasswordsMatch(false);
    }
  }

  async function createUser(userInput: UserCreate) {
    try {
      const userCredential = await createUserWithEmailAndPassword(auth, userInput.email, userInput.password);
      const customClaims = {
        role: userInput.role
      };

      if (userInput.role === "admin") {
        customClaims.role = "admin";
      } else if (userInput.role === "manager") {
        customClaims.role = "manager";
      }

      // await setCustomUserClaims(userCredential.user, customClaims);

      const userRef = ref(realTimeDatabase, "users");
      const newUser = {
        name: userInput.name,
        email: userInput.email,
        role: userInput.role,
        createdAt: new Date().toString(),
      };
  
      await push(userRef, newUser);
  
      setIsNewUserModalOpen(false);
      toast.success("Usuário cadastrado com sucesso");
    } catch (error) {
      console.log(error)
      toast.error("Ocorreu um erro ao cadastrar o usuário: " + error);
    }
  }

  return (
    <Dialog open={isNewUserModalOpen} onOpenChange={setIsNewUserModalOpen}>
      <DialogTrigger asChild>
        <Button onClick={handleOpenNewUserModal} className="bg-blue-800 text-gray-100 cursor-pointer">
          Criar Usuário
        </Button>
      </DialogTrigger>
      <DialogContent className="sm:max-w-[425px]">
        <DialogHeader>
          <DialogTitle>Criar Usuário</DialogTitle>
        </DialogHeader>
        <form onSubmit={handleCreateNewUser}>
          //... form
          <Button className='w-full'>Criar</Button>
        </form>
      </DialogContent>
    </Dialog>
  )
}

字符串
我的钩子/useAuth.tsx:

"use client"

import Cookie from "js-cookie"
import { User, onAuthStateChanged, signInWithEmailAndPassword, signOut } from "firebase/auth"
import { ReactNode, createContext, useContext, useEffect, useState } from "react"
import { auth } from "../lib/firebaseConfig"
import { useRouter } from "next/navigation"
import { toast } from "react-toastify"

interface CustomUser extends User {
  customClaims: {
    role: string;
  };
}

interface HandleLoginProps {
  email: string
  password: string
}

interface AuthContextData {
  handleLogin: (data: HandleLoginProps) => Promise<void>
  handleLogout: () => void
  isLoggedIn: boolean
  currentUser: any | null
  isLoading: boolean
  userRole: string | null;
}

interface AuthProviderProps {
  children: ReactNode;
}

const AuthContext = createContext<AuthContextData>({} as AuthContextData)

export function AuthProvider({ children }: AuthProviderProps) {

  const [currentUser, setCurrentUser] = useState<any | null>(null)
  const [isLoggedIn, setIsLoggedIn] = useState(false)
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState(false)
  const [userRole, setUserRole] = useState<string | null>(null);

  const router = useRouter()

  async function handleLogin({ email, password }: HandleLoginProps) {
    try {
      Cookie.set('auth_token', 'ckjnehcnhbecevbiutveiuvtvtuvhvxgvwiutve')
      const userCredential = await signInWithEmailAndPassword(auth, email, password)
      setIsLoggedIn(true)
      setCurrentUser(userCredential.user)
      router.push("/")
      toast.success("Seja bem vindo, " + email)
    } catch (error) {
      setError(true)
      toast.error("E-mail e/ou senha incorretos.")
      if (error instanceof Error) {
        console.log(error.message)
      } else {
        console.log("Unexpected error: " + error)
      }
    }
  }

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user: User | null) => {
      if (user) {
        const customUser = user as CustomUser;
        setCurrentUser(customUser)
        setIsLoading(false)
        setUserRole(customUser.customClaims?.role || null);
      } else {
        setIsLoggedIn(false)
      }
    })
    return () => {
      unsubscribe();
    };
  }, [])

  return (
    <AuthContext.Provider value={{ currentUser, isLoggedIn, userRole, isLoading, handleLogin, handleLogout }}>
      {children}
    </AuthContext.Provider>
  );

}

export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider component')
  }
  return context
}


我的侧边栏.tsx:

"use client"

import React, { forwardRef } from 'react';
import { User2, Home, ScrollText, Folders, FileCode2 } from "lucide-react";
import Link from "next/link";
import { useAuth } from "@/hooks/useAuth";

interface SidebarProps extends React.HTMLProps<HTMLDivElement> {
  showNav: boolean;
}

const Sidebar = forwardRef<HTMLDivElement, SidebarProps>((props, ref) => {

  const { currentUser, userRole } = useAuth()

  console.log("USER SIDEBAR: " + currentUser)
  console.log("USER SIDEBAR 2: " + userRole)
  return (
    <div ref={ref} className="fixed w-60 h-full bg-gray-200 shadow-sm">
      <div className="flex justify-center mt-6 mb-14">
        <picture>
          <img
            className="w-32 h-auto"
            src="/small-blue.png"
            alt="company logo"
          />
        </picture>
      </div>

      <div className="flex flex-col">
        <Link href="/">
          <div
            className={`pl-6 py-3 mx-5 rounded text-center cursor-pointer mb-3 flex items-center transition-colors text-blue-900 hover:bg-gray-100 hover:text-purple-500`}
          >
            <div className="mr-2">
              <Home className="h-5 w-5" />
            </div>
            <div>
              <p>Home</p>
            </div>
          </div>
        </Link>
        <Link href="/proposals">
          <div
            className={`pl-6 py-3 mx-5 rounded text-center cursor-pointer mb-3 flex items-center transition-colors text-blue-900 hover:bg-gray-100 hover:text-purple-500`}
          >
            <div className="mr-2">
              <FileCode2 className="h-5 w-5" />
            </div>
            <div>
              <p>Gerar Propostas</p>
            </div>
          </div>
        </Link>

        {userRole === "admin" ? (
          <>
            <Link href="/services">
              <div
                className={`pl-6 py-3 mx-5 rounded text-center cursor-pointer mb-3 flex items-center transition-colors text-blue-900 hover:bg-gray-100 hover:text-purple-500`}
              >
                <div className="mr-2">
                  <ScrollText className="h-5 w-5" />
                </div>
                <div>
                  <p>Serviços</p>
                </div>
              </div>
            </Link>
            <Link href="/services-types">
              <div
                className={`pl-6 py-3 mx-5 rounded text-center cursor-pointer mb-3 flex items-center transition-colors text-blue-900 hover:bg-gray-100 hover:text-purple-500`}
              >
                <div className="mr-2">
                  <Folders className="h-5 w-5" />
                </div>
                <div>
                  <p>Tipos de Serviços</p>
                </div>
              </div>
            </Link>
            <Link href="/users">
              <div
                className={`pl-6 py-3 mx-5 rounded text-center cursor-pointer mb-3 flex items-center transition-colors text-blue-900 hover:bg-gray-100 hover:text-purple-500`}
              >
                <div className="mr-2">
                  <User2 className="h-5 w-5" />
                </div>
                <div>
                  <p>Usuários</p>
                </div>
              </div>
            </Link>
          </>
        ) : ""}
      </div>
    </div>
  )
});

Sidebar.displayName = "SideBar";

export default Sidebar;


基本上,我有一个通过Firebase Realtime创建的用户,当创建一个新用户时,这也在Firebase Auth中注册。一切都很好,除了路由部分,我不能让管理员用户只访问主页和建议。我认为因为useRole为null:/
如果有人想帮助我,他们可以去:
https://proposals-mayarabecker-dev.vercel.app/
12345678

12345678
我的源代码在这里:
https://bitbucket.org/eltin182/proposals-mayarabecker-dev/src/main/frontend/
我希望我解释了我的问题和困难,如果你想要更多的信息,你可以问

svgewumm

svgewumm1#

Sidebar.tsx文件中,您是否尝试将usercustomUser变量记录到控制台,以查看它是否包含预期的用户详细信息。

相关问题