No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Roles y permisos

14/30
Recursos

Aportes 35

Preguntas 0

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

¡Wohoo! Soy el admin 😎 jajaja. Para el caso del final yo crearía roles. Asignarle roles a cada usuario (ya no solo un campo de user.isAdmin, sino un campo user.role) y con base en ese campo validar cada acción para saber si su rol le permite o no hacer X acción :p

Roles y permisos

La autorización es la que le permite a los usuarios tener cierto tipo de roles que nos permiten saber a que permisos tenemos acceso en nuestra aplicación, actualizar información o borrarla, tener permisos solo de lectura de la información, poder subir información etc .

Podemos tener distintos tipos de roles, por mencionar algunos pueden ser el administrador, moderadores, usuarios premiun o freemiun y demás.

Para empezar a implementar esta lógica de autorización vamos a nuestro archivo auth.js:

// Listas de Autorización
const adminList = [
  'RetaxMaster',
  'freddier',
  'juandc'
];

const AuthContext = React.createContext();

function AuthProvider({ children }) {
  /* Dentro de nuestra función de login veremos si el usuario a 
  ingresar es administrador */
  const login = ({ username }) => {
    /* Esta es la validación para saber si el usuario que ingresa a 
		la aplicación es administrador o no */
    const isAdmin = adminList.find(admin => admin === username);
    /* Ahora nuestros usuarios van a tener una propiedad para saber 
		si son admis */
    setUser({ username, isAdmin });
    navigate('/profile');
  }
}

Ahora en nuestra aplicación vamos a crear los permisos que van a tener nuestros administradores.

BlogPost.js

import { useAuth } from '../../auth/auth';

function BlogPost() {
	...	

  // Vamos a hacer uso de la autenticación
  const auth = useAuth();

	/* Aqui buscamos al momento de registrarnos si el usuario es parte 
  de la lista de administradores */
  const blogpost = blogdata.find( post => post.slug == slug );

  return (
    <>
      ...
      {/* Si nuestro usuario existe y es admin vamos a renderizar 
      este botón */}
      {auth.user?.isAdmin && (
        <button>Eliminar blogpost</button>
      )} 
    </>
  );
}

Ahora si entramos a alguno de los BlogPost con el nombre de algún administrador vamos a poder ver renderizado nuestro botón de eliminar BloPost.

Ya tenemos la lógica para mostrar contenido dependiendo del rol.

Ahora si vemos los blogpost que tienen autores, digamos que el autor del blog desea eliminarlo, pero ¿como lo va a hacer?

Vamos a crear la lógica para que los autores del contenido puedan eliminar sus propios Blogs.

function BlogPost() {
  ...

  /* Aquí validaremos si al acceder al blog soy el autor del blog y 
	pueda eliminarlo o tenga el rol de administrador para ello */
  const canDelete = auth.user?.isAdmin || blogpost.author === auth.user?.username;

  const returnToBlog = () => {
    navigate('/blog');
  }
  
  return (
    <>
      ...

      {/* Ahora vamos a preguntar si puedo borrar el BlogPost*/}
      {canDelete && (
        <button>Eliminar blogpost</button>
      )} 
    </>
  );
}

Ahora en caso de que seamos lo autores del Blog podemos eliminarlo, super fácil.

Pense que iba a ser complicado pero me resulto sencillo, cambiaria algunos nombres de los objetos.
Todavia tengo dudas de como se crean las API y los objetos JSON, si tienen una regla de planificar y organizar

const roles = {
    admin: {
        type: 'admin',
        read: true,
        write: true,
        delete: true
    },
    editor: {
        type: 'editor',
        read: true,
        write: true,
        delete: true
    },
    student: {
        type: 'student',
        write: false,
        read: true,
        delete: false
    },
}

const users = [{
    name: 'ivana',
    rol: roles.admin
},
{
    name: 'fred',
    rol: roles.admin
},
{
    name: 'cris',
    rol: roles.admin
},
{
    name: 'rocio',
    rol: roles.student
},
{
    name: 'leonel',
    rol: roles.editor
}]

const login = ({username}) => {
        const admin = users.filter(admin => admin.rol.type === 'admin')
        const isAdmin = admin.find(admin => admin.name === username)  . .. . . . .  }



//blogspot

    const canDelete = auth.user?.isAdmin || blogPost.author === auth.user?.username;

    // const filterA = auth.user?.admin?.filter( f => f.name && f.rol.delete)

    // console.log(filterA.find(f => auth.user?.username === f.name )) //devuelve un objeto con nombres de admin especificos

Reto

Mi solución al reto fue crear un arreglo de roles, con permisos anidados:

auth.js

const roles = [
  { role: "admin", update: true, delete: true },
  { role: "creator", update: true, delete: true },
  { role: "editor", update: true, delete: false },
];

Luego, crear una función para validad que el permiso que recibimos en la consulta existe:

function checkRoles(role) {
  return roles.find((item) => item.role === role);
}

Luego, crear un método que modifique el objecto user que se envia al provider:

function addPermissions(user) {
  if (!checkRoles(user.role)) return { ...user, update: false, delete: false };
  const role = roles.filter((item) => item.role === user.role);

  const newUser = { ...user, ...role[0] };
  return newUser;
}

Al crear hacer login quedaría así:

const login = ({ username }) => {

    let user = {
      username,
      isAdmin: false,
      role: "editor",
    };

    user = addPermissions(user);

    setUser(user);
    navigate("/profile");
  };

En componente de BlogPost solo debe de determinar que tipo de permisos tiene el usuario:
BlogPost.js

	const auth = useAuth();
	...
	{auth.user?.update && <button>Actualizar blogpost</button>}
	{auth.user?.delete && <button>Delete blogpost</button>}

Otra forma de saber si el usuario es admin:

const adminList = ["Irisval", "RetaxMaster", "freddier"]

const isAdmin = adminList.includes(username)

MDN - Array.prototype.includes

Leyendo varios aportes mi solución fue de la siguiente manera:

en Auth.js crear una constante para los roles y sus permisos respectivos;
y despues un array de los usuarios registrados

const roles = {
  admin: {
    write: true,
    read: true,
    delete: true,
  },
  editor: {
    write: true,
    read: true,
    delete: false,
  },
  visitor: {
    write: false,
    read: true,
    delete: false,
  },
};

const users = [
  {
    name: "Andres",
    role: roles.admin,
  },
  {
    name: "Felipe",
    role: roles.editor,
  },
];

Dentro de AuthProvider asignar el usuario y su rol

function AuthProvider({ children }) {
//............
  const login = (username) => {
    //revisar si el usuario existe o lo crea como visitante
    const rol = users.find((usu) => usu.name === username);
    rol !== undefined
      ? setUser(rol)
      : setUser({ name: username, role: roles.visitor });
    navigate("/profile");
  };
 //............
}

finalmente en BlogPost.jsx hacer las validaciones para renderizar un boton o no
dependiendo de los permisos de su rol

return (
    <>
      <h2>{blog.title}</h2>
      <button onClick={handleBack}>Back</button>
      <p>{blog.author}</p>
      <p>{blog.content}</p>
      {user?.role.write && <button>Edit Post</button>}
      {user?.role.delete && <button>Delete Post</button>}
      {user?.name === blog.author && (
        <>
          <button>Edit Post</button>
          <button>Delete Post</button>
        </>
      )}
    </>
  );

Les dejo un blogpost muy valioso donde explican un poco el tema de las rutas protegidas de una forma escalable. Espero les sirva como a mi 😉
https://www.robinwieruch.de/react-router-private-routes

Yo era de esos men, que tuvo su propio servidor Habbo Hotel. Este curso me recuerda mucho cuando modificaba y otorgaba los permisos como dueño de ese lugar.

Recuerdo que utilizaba PHP para quitar y poner.

!Ahhh, que recuerdos.!

Inspirado en unos de los compañeros dejo una simple y humilde solucion.

En el primero array determinaremos los roles y lo que pueden hacer.
En el segundo crearemos usuarios con roles.

const roles = {
    admin: {
        type: 'admin',
        read: true,
        write: true,
        delete: true
    },
    editor: {
        type: 'editor',
        read: true,
        write: true,
        delete: false
    },
}

const users = [{
    name: 'ivana',
    rol: roles.admin
},
{
    name: 'fred',
    rol: roles.admin
},
{
    name: 'leonel',
    rol: roles.editor
}]

En la funcion de autenticacion de Loguin agregaremos una linea extra para extraer el rol.

  const login = ({ username }) => {
        const Isrol = users.find(user => (user.name === username) ? user.rol : null)
        setUser({ username, Isrol })
        navigator('/profile')
    }

y al final, en el blogPost veremos si tienen autorizacion o no.

const authorityDelet = auth.user?.Isrol?.rol.delete
    const authorityEdit = auth.user?.Isrol?.rol.write
    return (
        <>
            <h2> {post.title}</h2>
            <p>{post.content}</p>
            <button onClick={returnToBlog}>Volver un paso atras</button>
            {authorityEdit && (
                <button>Editar blog</button>
            )}
            {authorityDelet && (
                <button>Eliminar blog</button>
            )}
        </>
    )
}

Para saber si un elemento existe en una lista podemos usar some de esta manera:

const isAdmin = adminList.some(admin => admin === username)

Apoyándome en los comentarios de los compañeros y lo aprendido en el curso, cree un array, que incluye a las personas autorizadas, los roles y permisos

const userType = [
    {
        name: ['Irisval', 'RetaxMaster', 'freddier'],
        role: 'admin',
        permissions: {
            create: true,
            read: true,
            update:false,
            delete: true,
        }
    },
    {
        name: ['yahe', 'pedro', 'pablo'],
        role: 'author',
        permissions: {
            create: true,
            read: true,
            update:true,
            delete: true,
        },

    },
];

Luego lo recorro para obtener las características del usuario(esto en la función login):

 let role = []
    let permissions = {}
    let userRole = ''
    const isAuthorized = userType.some(user => user.name.includes(username))

    userType.forEach((user) => {
      if (user.name.includes(username) && isAuthorized) {
        role.push(user.role)
        userRole = role.join()
        permissions = { ...user.permissions }
      } else if (!user.name.includes(username) && !isAuthorized) {
        role = ['visitor']
        userRole = role.join()
        permissions = {
          write: false,
          read: true,
          update: false,
          delete: false,
        }
      }
    })

setUser({ username, isAuthorized, userRole, permissions });
    navigate('/profile');

En el blog, de acuerdo a los permisos que tiene cada rol, cree una condición para que el administrador y el autor puedan borrar, pero solo el autor de cada blog, pueda editar

const postList = blogData.find(post => post.slug === slug)
   
 const canDelete = (auth.user?.isAuthorized && auth.user?.permissions.delete)&&
     (postList.author === auth.user?.username || auth.user.userRole === 'admin')

    const canUpdate = auth.user?.isAuthorized && auth.user?.permissions.update &&
    postList.author === auth.user?.username

El retorno quedaría así:

return (
        <>
          <h2>{postList.title}</h2>
          <button onClick={returnToBlog}>Volver al blog</button>
          <p>{postList.author}</p>
          <p>{postList.content}</p>
    
          {canDelete && (
            <button>Eliminar blogpost</button>
          )}

          {canUpdate && (
            <button>Editar blogpost</button>
          )}
        </>
      );

Roles y permisos

.
La autenticación consiste en identificar personas, saber quiénes acceden a una aplicación. Mientras que la autorización consiste en saber qué permisos tiene cada persona que haya pasado por la autenticación.
.
Vamos a dar ciertos permisos a los usuarios para que puedan eliminar o editar un blog según el rol que tengan en la aplicación.
.
Lo primero que hacemos en el archivo auth.js es crear una lista de administradores. Al momento de loguear un usuario, revisaremos si el usuario es un administrador. Entonces, ademas del nombre de usuario tendremos también una nueva propiedad llamada isAdmin que indica si se trata de un usuario administrador.
.

const adminList = ['Irisval', 'RetaxMaster', 'freddier'];

const AuthContext = React.createContext();

function AuthProvider({ children }) {
  ...
  const login = ({ username }) => {
    const isAdmin = adminList.find(admin => admin === username);
    setUser({ username, isAdmin });
    navigate('/profile');
  };
  ...
}

.
Posteriormente, desde BlogPost vamos a utilizar permisos para eliminar un blogpost. En primer lugar, hay 2 maneras para que alguien pueda eliminar un blogpost:
.

  • Un administrador puede eliminar cualquier blogpost.
  • Los usuarios pueden eliminar sus propios blogposts.

.
Por lo tanto, si se cumple alguna de estas condiciones, guardaremos el resultado en la variable canDelete, lo que indicará si podemos renderizar un botón para eliminar un blogpost.
.

function BlogPost() {
  const navigate = useNavigate();
  const { slug } = useParams();

  const auth = useAuth();

  const blogpost = blogdata.find(post => post.slug === slug);

  const canDelete = auth.user?.isAdmin || blogpost.author === auth.user?.username;

  const returnToBlog = () => {
    navigate('/blog');
  };
  
  return (
    <>
      <h2>{blogpost.title}</h2>
      <button onClick={returnToBlog}>Volver al blog</button>
      <p>{blogpost.author}</p>
      <p>{blogpost.content}</p>

      {canDelete && (
        <button>Eliminar blogpost</button>
      )}
    </>
  );
}

export { BlogPost };
Ya no definiría si el usuario puede hacer tal acción dentro de la app basado en isAdmin. Para ello generaria una estructura de roles donde se definan desde allí los permisos que aplican para cada uno de ellos, así basado en el rol que tenga cada usuario será las acciones que tienen permitidas.

yo lo hice asi

Yo lo realice de la siguiente manera, me guie de unos de los post de los companeros y fui desarrollando mi idea, si tienen alguna sugerencia por favor me lo pueden comentar abajo. Como primera medida cree un archivo js para manejar los roles y la asignacion de estos en un objeto ![](https://static.platzi.com/media/user_upload/image-fa0083e1-80ae-4e66-9a6b-8b1f01ab6816.jpg) Despues realice el manejo del estado en el archivo auth dependiendo del rol que tenga cada usuario ![](https://static.platzi.com/media/user_upload/image-73dd3ce0-57e0-4928-beb7-23fce39ebfb4.jpg) Me tomo bastante tiempo encontrar esta solucion pero me siento satisfecho de como lo maneje. Gracias

Mi solución

En mi experiencia diseñando interfaces de administración puedo decir que normalmente la información sobre el tipo de permisos que un usuario viene en un solo arreglo dentro de un json, no es recomendable ni practico crear listas separadas para administradores, autores o usuarios normales. Esto debido a que en cualquier tipo de iteración tendríamos que hacerlo sobre cada lista que tengamos, la cual va a variar según los tipos de permisos que nos soliciten afectando seriamente el performance de nuestra aplicación.
Siguiendo esta idea en auth.js creé una lista mixta en donde pueden venir 3 tipos de roles:

  • admin
  • author
  • reader

auth.js

import React, { useContext } from "react";
import { Navigate, useNavigate } from "react-router-dom";

const AuthContext = React.createContext()
const users = [
    {
        username: 'Cortana',
        type: 'admin',
    },
    {
        username: 'Jhon117',
        type: 'admin',
    },
    {
        username: 'Nicobytes',
        type: 'author',
    },
    {
        username: 'diannerd',
        type: 'author',
    },
    {
        username: 'JuanDc',
        type: 'author',
    },
    {
        username: 'Mixtaco',
        type: 'reader',
    }
]

function AuthProvider({ children }){
    const navigate = useNavigate()

    const [user, setUser] = React.useState(null);

    const assignRole = username => {
        const userFound = users.filter( user => user.username === username )
        return userFound.length > 0 ? userFound[0].type : 'reader'
    }

    const login = ({ username }) => {
        const role = assignRole(username)
        
        setUser({ username, role })
        navigate('/profile')
    }

    const logout = () => {
        setUser(null)
        navigate('/')
    }

    const auth = { user, login, logout };
    
    return(
        <AuthContext.Provider value={auth}>
            { children }
        </AuthContext.Provider>
    )
}

function useAuth(){
    const auth = useContext(AuthContext);
    return auth;
}

function RedirectToLogin(props){
    const auth = useAuth();

    if (!auth.user) {
        return <Navigate to='/login' />;
    }

    return props.children;
}

export {
    AuthProvider,
    useAuth,
    RedirectToLogin
}

Ahora yo cambie la búsqueda que el profe propone por una función llamada assignRole la cual retorna el nombre del tipo de permiso que le hemos asignado (Se supone que esta información esta almacenada en una base de datos(DB) ). Como nuestra aplicación acepta cualquier nombre como autenticación en caso de no encontrarse en la DB, le asignamos permisos de solo lectura para evitar errores a futuro.
Ahora mi setUser actualiza pasando el username y rol independientemente de cual sea.

BlogPost.js

import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { blogdata } from "./blogdata";
import { useAuth } from "./auth";

export default function BlogPost(){
    const { slug } = useParams();
    const navigate = useNavigate();
    const auth = useAuth()

    const blogpost = blogdata.find( post => post.slug === slug);

    const goToBack = () => {
        navigate(`/blog`);
    }

    return(
        <>
            <h2>
                { blogpost.title }
            </h2>
            <button onClick={goToBack}>
                Regresar
            </button>
            <p>
                { blogpost.content }
            </p>
            <p>
                { blogpost.author }
            </p>

            {(auth.user?.role === 'admin' || (auth.user?.role === 'author' && auth.user?.username === blogpost.author)) && (
                <button>
                    Eliminmar post
                </button>
            )}
            {(auth.user?.role === 'author' && auth.user?.username === blogpost.author) && (
                <button>
                    Editar post
                </button>
            )}
        </>
    )
}

Nos pasamos a BlogPost.js para hacer la comprobación y mostrar un botón, para mostrarse debe cumplir con los siguientes puntos:

  1. Que tengamos un usuario autenticado.
  2. Que nuestro rol para eliminar sea “admin” o “author”.
  3. En caso de que tengamos permisos de author, nuestro usuario sea autor del post.

Nuestra comprobación queda algo larga, pero esto nos refuerza la seguridad, pues aunque nuestro usuario sea el autor del post debe tener permisos de edición habilitados.
Esta lógica nos abre la puerta para agregar un botón de edición el cual ni siquiera los “admins” pueden tener. Unicamente el autor del post puede tener este poder.
Esto nos abre la posibilidad de observar el tipo de usuario desde Profile.js y no andar haciendo console.logs para saber que tipo de usuario tenemos

Profile.js

import React from "react";
import { useAuth } from "./auth";

export default function ProfilePage(){
    const auth = useAuth()

    return(
        <>
           <h2>Profile</h2>
           <h3>
                {auth.user.username}
           </h3>
           <h4>
              Permisos:  {auth.user.role}
           </h4>
        </>
    );
}

Esta fue la mejor solucion que pude encontrar al reto
.
Inicialmente defini los valores de entrada. Con los diferentes array de roles, los junte en un unico array ya que me parecio la manera mas comoda de trabajar.

Luego, lo que hice fue crear una variable para saber quien es el usuario actual y verificar que exista en alguna de las listas.
.
Para ello utilice el metodo some para que recorriera el Array de arrays y dentro del mismo para cada Array busque aquel que con el metodo find contenga el usuario que se loggeo con el auth.
.
Asi pude sobreescribir la variable currentUser y con esta acceder usando la notacion de corchetes para poder usar una variable en el objeto skills que contiene los diferentes roles con su respectivo componente (en este caso estructura html) a renderizar deacuerdo a sus funciones


Finalmente use esa variable dentro de un div cualquiera en el componente BlogPost y de esta manera sea cual sea el rol se va a renderizar el componente asociado a este rol

Código de la clase en TypeScript :

interface User {
    username: string,
    isAdmin: boolean,
}

interface Auth {
    user: User | null
    login: (value: Omit<User, "isAdmin">) => void
    logout: () => void
}

const adminList = ['Max', 'Tony', 'Steve'];

const AuthContext = createContext({} as Auth);

const AuthProvider: FC<PropsWithChildren> = ({ children }) => {

    const navigate = useNavigate();
    const [user, setUser] = useState<Auth['user']>(null);

    const login: Auth['login'] = ({ username }) => {
        const isAdmin = adminList.some(admin => admin === username);
        setUser({ username, isAdmin });
        navigate('/profile')
    }

    const logout: Auth['logout'] = () => {
        setUser(null);
        navigate('/');
    }

    const auth: Auth = { user, login, logout }

    return (
        <AuthContext.Provider value={auth}>
            {children}
        </AuthContext.Provider>
    )
}

BlogSpot:

type IBlogPostParams = {
    slug: string
}

const BlogPost = () => {

    const navigate = useNavigate();
    const { slug } = useParams<IBlogPostParams>();

    const { user } = useAuth();

    
    const blogpost = blogdata.find(post=> post.slug === slug);

    const canDelete = user?.isAdmin || blogpost?.author === user?.username;

    const returnToBlog = () =>{
        navigate('/blog');
    };

    return(
        <>
            <h2>{ blogpost?.title }</h2>
            <button onClick={returnToBlog}>Volver al blog</button>
            <p>{ blogpost?.content }</p>
            <p>{ blogpost?.author }</p>

            {canDelete && (
                <button>Eliminar blogpost</button>
            )}
        </>
    )
}

Yo lo que hice fue crear:

const roles = [
    { role: adminList, permiso: true },
    { role: blogdata.author, permiso: true },
    { role: "editor", permiso: false },
];

tambien importe el archivo blogdata para tener los datos del autor

    const login = ({ username }) => {
        const isAdmin = adminList.find(admin => admin === username)
        const isRoles = roles.find(role => {
            if (role.role === username && role.permiso === true) {
                return true
            }
        })
        setUser({ username, isAdmin, isRoles });
        navigate('/profile')
    }

despues solamente cree una funcion nueva con un if.
en otro archivo a la variable canDelete le agregue:
auth.user?.isRoles y listo por ahora me funciona bien

Mi solución al reto fue crear un objeto permissions que será una propiedad más del estado user que ya existía en auth.js :

        const permissions = {
            create: false,
            edit: false,
            delete: false,
            approve: false,
            reject: false
        };

        if(isAdmin){
            permissions.create = true;
            permissions.edit = true;
            permissions.delete = true;                       
        }
        else if(isEditor)
        {
            permissions.create = true;
            permissions.edit = true;
        }
        else if(isApprover)
        {
            permissions.approve = true;
            permissions.reject = true;
        }     
        
        setUser({ userName, isAdmin, permissions });

En el componente BlogPost se realizan las validaciones contra permissions para definir si se puede editar, eliminar, aprobar o rechazar un determinado post:

    const statusIsUndefined = !blogPost.approved && !blogPost.rejected;    
    const canDelete = auth.user?.permissions?.delete && 
            (auth.user?.isAdmin || auth.user?.userName === blogPost.author) ;
    const canEdit = auth.user?.permissions?.edit && 
            (auth.user?.isAdmin || auth.user?.userName === blogPost.author) ;
    const canApprove = auth.user?.permissions?.approve && statusIsUndefined;
    const canReject = auth.user?.permissions?.reject && statusIsUndefined;

I N C R E I B L E

Como suele pasar, hay roles que tienen diferentes permisos, yo lo organicé de esta manera:

Creo que el poner false no es necesario, si no lo encuentres es false y si lo encuentra es true
lo hice pensando en como podría estar organizado Platzi 💚

const estudiantes = {
  free: {
    cursos: {
      curos_free: true
    }
  },
  mensual: {
    cursos: {
      curos_free: true,
      curos_pago: true
    },
    certificado: true
  },
  anual: {
    cursos: {
      curos_free: true,
      curos_pago: true,
      eventos: true
    },
    certificado: true
  }
}

Podria ser algo asi:

{
    admin: {
        create: true,
        view: true
        edit: true,
        delete: true
    },

    writter: {
        create: true,
        view: true,
        edit: true,
        delete: false
    },

    ...
}

Yo simplemente cree más arrays e hice más comprobaciones. Tiene más sentido agregar una propiedad “role” al user, pero ya que es un proyecto de prueba y que la lógica es casi la misma lo dejé así.

import React from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { blogData } from '../data/blogdata'
import { useAuth } from './auth'

export function BlogPost() {
    const navigate = useNavigate()
    const { slug } = useParams()
    const { user } = useAuth()

    const blogPost = blogData.find(post => post.slug === slug)
    const userIsAuthor = user?.username === blogPost.author
    const userIsEditor = user?.isEditor
    const userIsSpellChecker = user?.isSpellChecker
    const userIsModerator = user?.isModerator

    const returnToBlog = () => {
        navigate('/blog')
    }

    return (
        <>
            <button onClick={returnToBlog}>return to blog</button>
            <h2>{blogPost.title}</h2>
            <li>{blogPost.author}</li>
            <p>{blogPost.content}</p>

            {userIsModerator && <button>mark as best</button>}
            {(userIsModerator || userIsAuthor) && <button>delete blog</button>}
            {(userIsEditor || userIsAuthor) && <button>Modify blog</button>}
            {(userIsSpellChecker || userIsAuthor) && (
                <button>Correct spelling</button>
            )}
        </>
    )
}

Para el reto decidi crear un botón editar el cual solo debe aparecer a usuarios administradores y editores sin embargo los editores no podrán ver el botón eliminar post.

para ello la lista adminList la convertí en un array de objetos

const adminList = [
  { username: 'almendev', role: 'admin' },
  { username: 'juandc', role: 'editor' },
  { username: 'freddier', role: 'admin' }
]; 

ahora los usuarios tiene un rol que identificar al momento de renderizar ciertas funciones, ahora la función login queda de la siguiente manera

const login = ({ username }) => {
    const user = adminList.find(admin => admin.username === username) || { username: username, role: 'user' };
    setUser(user);
    navigate('/profile');
  };

de esta forma si el usuario esta dentro del array de usuarios con permisos especiales se retornada el objeto con su username y role sino se retornara un objeto que contiene los mismo campos sin embargo atribuye un rol por defecto llamado user por poner un nombre.

con esto hecho ya solo queda modificar el componente blogpost,
para lo cual ajuste el canDelete para que valide ahora el atributo role.

const canDelete = auth.user?.role === 'admin' || blogpost.author === auth.user?.username;

y para los editores agregue el siguiente condicional

{auth.user?.role === 'editor' && (
        <button>Editar</button>
      )}

si esta forma de hacerlo no es correcta me gustaría me ayuden con sus comentarios a mejorar.

Gracias

// Crear una lista nueva en auth.js
const editorList = ['Francisco', 'Javier', 'Luis'];
// Hacer la validación de su nombre y agregarlo al actualizador 
const isAdmin = adminList.find(admin => admin === username);
const isEditor = editorList.find(editor => editor === username);
setUser({ username, isAdmin, isEditor });

// Hacer la validación en BlogPost, creando una variable nueva
const canEdit = auth.user?.isEditor || blogpost.author === auth.user?.username;
// Crear un botón nuevo, para editores.
{canEdit && (
        <button>Editar blogpost</button>
      )}

Esta es mi solución al reto de Juan:

separe la lista de personas y lo coloque en un archivo whiteList.js simplemente para tener un poco más de orden

const whiteList = []

whiteList.push({
    name: 'eze',
    role: {
        admin: true,
        editor: true,
        QA: true,
        student: false,
    },
})


whiteList.push({
    name: 'agos',
    role: {
        admin: true,
        editor: true,
        QA: true,
        student: false,
    },
})


whiteList.push({
    name: 'nano',
    role: {
        admin: false,
        editor: true,
        QA: false,
        student: false,
    },
})


whiteList.push({
    name: 'erika',
    role: {
        admin: false,
        editor: true,
        QA: false,
        student: false,
    },
})

whiteList.push({
    name: 'marce',
    role: {
        admin: false,
        editor: false,
        QA: true,
        student: false,
    },
})


whiteList.push({
    name: 'alguien',
    role: {
        admin: false,
        editor: false,
        QA: true,
        student: false,
    },
})

whiteList.push({
    name: 'pepito',
    role: {
        admin: false,
        editor: false,
        QA: true,
        student: false,
    },
})

whiteList.push({
    name: 'otros',
    role: {
        admin: false,
        editor: false,
        QA: false,
        student: true,
    },
})

export { whiteList }

Luego, en nuestro archivo auth.js realice los siguientes cambios:

import { whiteList } from './whiteLIst'

const AuthContext = React.createContext()

function AuthProvider({ children }) {
    const navigate = useNavigate()
    const [user, setUser] = useState(null)


    const roleOf = (username) => {
        const user = whiteList.find(item => username === item.name )
        if (user) return user.role
        return {
            admin: false,
            editor: false,
            QA: false,
            student: true,
        }
    } 
    
    const login = ({ username }) => {
        const autorization = roleOf(username)
        setUser({ username, autorization })
        navigate('/profile')
    }

   ...
}

por último, en el archivo Blogpost.jsx hice las validaciones:

import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useAuth } from './auth';
import { blogData } from './blogData'

function BlogPost() {
    const navigate = useNavigate()
    const { slug } = useParams();
    const auth = useAuth()

    const blogpost = blogData.find( post => post.slug === slug );

    const isAdmin = auth.user.autorization.admin

    const isEditor = auth.user.autorization.editor || blogpost.author === auth.user?.username

    const isQA = auth.user.autorization.QA

    const isStudent = auth.user.autorization.student

    const returnToBlog = () => {
        navigate('/blog')
    }
    
    return (
    <>
        <h2>{blogpost.title}</h2>
        <button onClick={returnToBlog} >volver al blog</button>
        <p>{blogpost.author}</p>
        <p>{blogpost.content}</p>

        {(isAdmin || isEditor) && (
            <button>Eliminar blogpost</button>
        )}

        {(isAdmin || isQA) && (
            <button>El blog es correcto</button>
        )}

        {(isStudent) && (
            <button>Me gusta</button>
        )}
    </>
    );
}

export { BlogPost }

Esta fue la solución que se me ocurrió, si se te ocurre algo mejor no dudes en dejarlo en los comentarios 😄

Yo lo que haria es que tendria un useState o un useReducer para cada role y dependiendo del rol se renderice una UI u otra

Para el reto podemos hacer asi:

// App.js

<Route path="/" element={<RequireAuth allowedRoles={['member', 'root']} />}>
	<Route index element={<Home />} />
	{/* Mis rutas */}
</Route>

// RequireAuth.js

const RequireAuth = ({ allowedRoles }) => {
  return isAuthenticated ? (
    <Layout>
       {
         allowedRoles.includes(roleUser) ? (
           <Outlet />
         ) : (
           <Navigate to="/unauthorized"  />
         )
        }
    </Layout>
    : (
      <Navigate to="/auth/login"  />
    )
}

export default RequireAuth
Geniiaaal!! INTERSECTANDO PUNTOS ENTRE EL BACKEND Y EL FRONTEND. Authorization: Desde el backend muchas veces usamos roles para verificar la autorización. Los cuales se compactan en un token con librerías como JWT. esto es un token: `dak3128u3dq.ej89qjndqoiow.mjod0au9221` Una vez el usuario se autentica, el backend le provee temporalmente un refresh-token(un token que tiene un tiempo limitado para utilzarse). es decir, un objeto {role: 'customer'} o {role: 'admin', como también la fecha y mas detalles}. Esta información se la enviamos al frontend cuando este se logea con éxito. el frontend(el cliente) almacena esta información en por ejemplo un localstorage o un sesion storage! por fin entendimos para que existe el sesion storage jajaja Una vez que el usuario auntenticado se mueva por la pagina, se hara desde el frontend un fetch a una API. donde por medio de los famosos HEADERS, el frontend enviara el famoso TOKEN con los roles, el backend rápidamente lo verifcara este mismo token. y le dará okay si esta autorizado O no para entrar a esa ruta de la API del back.
Esta es mi solución, les servirá mucho si trabajan con Typescript, ya que el tipado correcto fue importante para poder solucionar este ejercicio. Básicamente cree un objeto cuyos elementos contenían: userName y userType, y apartir del userType cree un "editor" que puede editar artículos pero no eliminarlos. \*\*\*Si tienen problema leyendo el código, pues me traduce los caracteres especiales a una notación distinta, pueden pasar el código ChatGPT para que se los "traduzca" : ```ts // auth.tsx import React, { createContext, ReactNode, useContext, useState } from 'react'; import { useNavigate, Navigate } from 'react-router-dom'; // aqui se define que tipo de dato es children type AuthProviderProps = { children: ReactNode; } // aqui se definen los tipos de datos que reciben las funciones y useState de Auth Provider type AuthContextType = { // el tipo de dato es un objeto con un tipo string o un valor nulo user: {username: string, specialUser: string | undefined, userTypeName: string | undefined } | null; // login recibe una funcion funcion con "credenciales" que son un objeto con un dato tipo string login: (credentials: { username: string }) => void; // logout recibe un valor del tipo funcion logout: () => void; } // esto de beria de ser trabajo del backend pero vamos a suponer que nos lo da el backend const specialUsers = [ { userName: 'Irisval', userType: 'admin' }, { userName: 'dr1602', userType: 'admin' }, { userName: 'Arti', userType: 'reviewer' }, ] // aqui dice que createContext puede recibr los tupos definidos en AuthContextType o puede ser indefinido, y por default lo haces indefinido const AuthContext = createContext<AuthContextType | undefined>(undefined); // React FC ayuda en typescript a definir un Function Component y permite pasar props prededifinas, en este caso es necesario para definir a "children" const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => { const navigate = useNavigate(); const [ user, setUser ] = useState<{ username: string, specialUser: string | undefined, userTypeName: string | undefined } | null>(null); const login = (credentials: { username: string }) => { const isSpecialUser = specialUsers.find( admin => admin.userName === credentials.username ) setUser({ username: credentials.username, specialUser: isSpecialUser?.userName, userTypeName: isSpecialUser?.userType }) navigate('/profile'); } const logout = () => { setUser(null) navigate('/'); } const auth = { user, login, logout } return( <AuthContext.Provider value={auth}> {children} </AuthContext.Provider> ) } const useAuth = () => { const auth = useContext(AuthContext); return auth; } const ProtectedRoute = (props: any) => { const auth = useAuth(); if (!auth?.user) { return <Navigate to='/login'/> } return(props.children) } export { AuthProvider, useAuth, ProtectedRoute } // BlogPost.tsx import React from "react" import { useNavigate, useParams } from 'react-router-dom' import { useAuth } from './../hooks/auth' import blogdata from './../../data/blogdata' const BlogPost: React.FC =() => { const navigate = useNavigate(); const { slug } = useParams(); const auth = useAuth() const blogpost = blogdata.find( post => post.slug === slug ); const shouldDelete = auth?.user?.userTypeName !== 'reviewer' && auth?.user?.specialUser || blogpost?.author === auth?.user?.username ; const shouldEdit = blogpost?.author === auth?.user?.username || auth?.user?.userTypeName === 'reviewer'; const returnBLog = () => { navigate(-2) } return( <>

{blogpost?.title}

{blogpost?.author}

{blogpost?.content}

{ shouldDelete && ( <button> Eliminar Post </button> ) } { shouldEdit && ( <button> Editar Post </button> ) } <button onClick={returnBLog} > Volver </button> ) } export { BlogPost } ```
Ya lo habia implementado de antemano y lo que hago es que en la asignación de roles se de mediante nombre rol, lo que me permitiria que mediante el nombre digitado por el usuario si coincide con alguno de la lista le asigne su correspondiente rol o sino existe sea user, en el tema de definir si es admin, editor o lo que sea verifico el rol que ya le asignamos y si corresponde con la categoria buscada nos retornaría true, y ya en su uso llamo la función en donde si si es admin o los otros roles renderize X o Y informacion ![](https://static.platzi.com/media/user_upload/image-df6566c7-89c8-4d6d-9dc7-9138e2c01d82.jpg) ![](https://static.platzi.com/media/user_upload/image-91ceebef-2dfb-44f3-9307-f630d2fc8ff9.jpg)![](https://static.platzi.com/media/user_upload/image-6ef265cb-e69f-4d0a-82b2-558e768e2338.jpg)![](https://static.platzi.com/media/user_upload/image-701635a5-6ebe-40b0-942e-a7c663c2fc8b.jpg)
```js // Cambiar adminList por userdata const userdata = [ { username: 'Jose', accessLevel: 'user', }, { username: 'Iris', accessLevel: 'admin', }, { username: 'Retax', accessLevel: 'developer', }, { username: 'Juan', accessLevel: 'teacher', }, ]; //cambiar isAdmin por funcion function findAccessLevel (loginUsername) { let data = null userdata.forEach(element => { if (element.username === loginUsername) { data = element.accessLevel } }) return data } //Asignar distintos botones para cada tipo de usuario en BlogPost.js if (blogpost.author === auth.user?.username) { accessOptions.push('delete') accessOptions.push('edit') } else if (auth.user.accessLevel === 'admin'){ accessOptions.push('delete') } else if (auth.user.accessLevel === 'developer'){ accessOptions.push('change page layout') } else if (auth.user.accessLevel === 'teacher'){ accessOptions.push('report') accessOptions.push('reply') } else { accessOptions.push('report') } //Llamar en return return ( <> ... { accessOptions.map(text => ( <button>{text}</button> )) } ) ```No creo que sea el método más óptimo, pero ya ire pensando en cambiarlo si es necesario

Me pueden ayudar un poco, cuando me registro no sale el nombre del registrado xd

y también cuando cierro sesión no se quita el loguot :C
https://github.com/SRgenius1/Project-with-react-router/tree/main/components