No tienes acceso a esta clase

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

Control de roles

12/20
Recursos

Aportes 11

Preguntas 5

Ordenar por:

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

Yo diria que es un 403, no tiene permisos de acceso. 401 yo lo uso solo para si es sesion o no.

Se debe trabajar en la gestión de permisos y roles ya que no todos deben poder crear categorías o crear usuarios, únicamente un usuario administrador podría hacer eso.

Se crea un middleware que verifique que tipo de rol es, y lo que lo deje seguir o no.

En auth.handler.js se crea la función checkAdminRole la cual verificará si el rol del usuario es admin o customer, si es admin entonces pasa al siguiente middleware, de lo contrario lanza un error unauthorized.

function checkAdminRole(req, res, next) {
  const user = req.user;
  if (user.role === 'admin') {
    next();
  } else {
    next(boom.unauthorized());
  }
}

A la ruta se le agrega el middleware, la lógica es:

  1. Autenticar, verificar el token y obtener los datos del user (*passport.authenticate)*.

  2. Verificar el tipo de rol de user (checkAdminRole).

  3. Validar los datos del body (validatorHandler).

  4. Conectarse al servicio.

router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  checkAdminRole,
  validatorHandler(createCategorySchema, 'body'),
  async (req, res, next) => {
    try {
      const body = req.body;
      const newCategory = await service.create(body);
      res.status(201).json(newCategory);
    } catch (error) {
      next(error);
    }
  }
);

Cuando se desea escalar y tener más roles, la función checkAdminRole se vuelve poco mantenible, por ello creamos la función checkRoles que recibirá los roles que tendrán acceso a ese endpoint. Si en los roles se encuentra el rol del usuario, devolverá true y tendrá acceso al endpoint, de lo contrario devolverá false y arrojará error unauthorized.

En resumen, la función checkRoles recibe un array de roles, verifica que user.role se encuentre en ese array, y si todo bien procede al siguiente middleware.

function checkRoles(...roles) {
  return (req, res, next) => {
    const user = req.user;
    if (roles.includes(user.role)) {
      next();
    } else {
      next(boom.unauthorized());
    }
  };
}

Haciendo la implementación del middleware en la ruta queda de la siguiente manera, agregando los roles a los que tendrá acceso ese endpoint.

router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  checkRoles('admin', 'seller'),
  validatorHandler(createCategorySchema, 'body'),
  async (req, res, next) => {
    try {
      const body = req.body;
      const newCategory = await service.create(body);
      res.status(201).json(newCategory);
    } catch (error) {
      next(error);
    }
  }
);

Se recomienda utilizar la librería accesscontrol donde realmente y de forma explicita se gestionan permisos de una forma más profunda y avanzada.

To create a role control, a middleware will be our best friend

First of all, let’s think about what we need, we need a middleware able to check what kind of users is authenticated, for this, let’s create a single function, which will receive a list of roles and return a middleware.

//@param roles array of roles that can access the route
function checkRole(roles) {

    return (req, res, next) => {
        const user = req.user;
        //Check if rol is allowed to access
        if (!roles.includes(user.role)) {
            next(boom.unauthorized("Unauthorized!You cannot do this, Admins have been notified "));
        } else {
            //If everything is right, go to next middleware
            next();
        }
    }
}

Finally just use it wherever you need to check role, por example:

router.post("/",
    passport.authenticate('jwt', { session: false }),
    checkRole(['admin', 'Captain']),
    validatorHandler(getOrderSchema, "body"),
    async(req, res, next) => {
        try {
            const body = req.body;
            res.status(201).json(await service.create(body));
        } catch (error) {
            next(error);
        }
    }
);

Recommendation

Acces Control NPM

Haciendo que esto sea un poco más escalable y mejores prácticas:
Creé un archivo index.js dentro de una carpeta ‘roles’ y simplemente es esto:

const ROLES = {
  ADMIN: 'admin',
  USER: 'user',
  CUSTOMER: 'customer',
  SELLER: 'seller'
}

module.exports = ROLES

luego usamos esos valores en los archivos que querramos:

const ROLES = require('../roles/');

router.get('/',
  protectRoute,
  checkRoles(ROLES.ADMIN, ROLES.CUSTOMER, ROLES.SELLER),
  async (req, res, next) => {
  try {
    const categories = await service.find();
    res.json(categories);
  } catch (error) {
    next(error);
  }
});

Si estan usando insomia para hacer la pruebas de la API, pueden guardar el token del usuario al hacer login. Pega la siguiente linea, dentro del archivo de variables de entorno

{
	"TOKEN_ADMIN": "{% response 'body', 'req_1e7f7e044bd5407bbff202ad27392f6e', 'b64::JC50b2tlbg==::46b', no-history, 60 %}",
}

Nota: después de pegar la línea de código aparecerá un recuadro en rojo, no se asusten, deben configurar los parámetros según sus request disponible (esto se hace a traves de una ventana gráfica)

  • Function to Perform: deber ser response (origen de la info que se desea guardar)
  • Attribute: body ( que parte especifica del response contiene la info)
  • Request: [Auth] POST Login Admin, este el nombre de mi request
  • Filter: este es valor que deseamos guardar en la variable de entorno, en este caso es $.token
  • Trigger behavior: acción a realizar una vez obtenida la info, en este caso No History, ya que solo queremos las info guardada en token

No se si alguien ya lo puso antes, pero hice un pequeño cambio al código para no poner un rol que de por si tendrá acceso a todo ( al menos en este caso) para no repetir “admin” cada que se llama a la función hice lo siguiente:

function checkRoles(...roles){
    roles.push('admin');
    return (req, res, next) => {
        const user = req.user;
        if(roles.includes(user.role)){
        next()
        } else {
        next(boom.unauthorized());
        }
    }
}

👮‍♀ Clase #12: Control de roles 12/20 👮

 

Pasos: 📝

 

  • Vamos a implementar un control para el acceso de las peticiones, es decir, si se establece que según el tipo de role (‘admin’, ‘customer’, ‘seller’) tiene privilegios para acceder a las rutas como crear categorías, ver las categorías almacenadas, editar y eliminar categorías.
     
    Para ello, vamos a VSC, entramos a la carpeta middlewares, abrimos el archivo auth.handler.js, se implementan las funciones para chequear si el role del usuario que hizo el login tiene permitido hacer la petición, las funciones checkAdminRole y checkRoles quedan:
     
//Ésta función solo evalúa para el role ‘admin’
function checkAdminRole(req,res,next){
	console.log(req.user);
	//Se encuentra el payload
	const user=req.user;
	if(user.role==='admin'){
		next();
	}else{
		next(boom.unauthorized());
	}
}

//Los 3 puntos transorma todo argumento en array
function checkRoles(...roles){
	return(req,res,next)=>{
		const user=req.user;
		//Compara el role del usuario con los roles permitidos enviados
		if(roles.includes(user.role)){
			next();
		}else{
			next(boom.unauthorized());
		}
	}
}

module.exports={checkApiKey,checkAdminRole,checkRoles}

 
Guardamos, vamos a la carpeta routes y abrimos el archivo categories.router.js, se implementa la lógica en cada petición, el código queda:
 

const express = require('express');
const passport = require('passport');

const CategoryService = require('./../services/category.service');
const validatorHandler = require('./../middlewares/validator.handler');
//Para veriicar si tiene autorización:
const { checkRoles } = require('./../middlewares/auth.handler');
//const { checkAdminRole } = require('./../middlewares/auth.handler');
const { createCategorySchema, updateCategorySchema, getCategorySchema } = require('./../schemas/category.schema');

const router = express.Router();
const service = new CategoryService();

router.get('/',
	passport.authenticate('jwt', {session: false}),
	checkRoles('admin', 'seller', 'customer'),
	async (req, res, next) => {
		try {
			const categories = await service.find();
			res.json(categories);
		} catch (error) {
			next(error);
		}
});

router.get('/:id',
	passport.authenticate('jwt', {session: false}),
	checkRoles('admin', 'seller', 'customer'),
	validatorHandler(getCategorySchema, 'params'),
	async (req, res, next) => {
		try {
			const { id } = req.params;
			const category = await service.findOne(id);
			res.json(category);
		} catch (error) {
			next(error);
		}
	}
);

router.post('/',
	//Proteger este endpoint con passport
	passport.authenticate('jwt', {session: false}),
	//Verificar si está autorizado con el role de 'admin'
	//checkAdminRole,
	checkRoles('admin'),
	validatorHandler(createCategorySchema, 'body'),
	async (req, res, next) => {
		try {
			const body = req.body;
			const newCategory = await service.create(body);
			res.status(201).json(newCategory);
		} catch (error) {
			next(error);
		}
	}
);

router.patch('/:id',
	passport.authenticate('jwt', {session: false}),
	checkRoles('admin', 'seller'),
	validatorHandler(getCategorySchema, 'params'),
	validatorHandler(updateCategorySchema, 'body'),
	async (req, res, next) => {
		try {
			const { id } = req.params;
			const body = req.body;
			const category = await service.update(id, body);
			res.json(category);
		} catch (error) {
			next(error);
		}
	}
);

router.delete('/:id',
	passport.authenticate('jwt', {session: false}),
	checkRoles('admin', 'seller'),
	validatorHandler(getCategorySchema, 'params'),
	async (req, res, next) => {
		try {
			const { id } = req.params;
			await service.delete(id);
			res.status(201).json({id});
		} catch (error) {
			next(error);
		}
	}
);

module.exports = router;

 
Guardamos, si no se ha compilado con: npm run dev en la terminal, esperar a Mi port 3000
 

  • Vamos a Insomnia, entramos a la carpeta Auth, duplicamos el Login y lo editamos y colocamos en uno: Login Admin y Login Customer.
     
  • Consultamos en la petición POST de la carpeta Customer el último cliente creado, y con los datos hacemos un nuevo Login en Auth, el body en mi caso:
     
{
	"email": "[email protected]",
	"password": "23 23 23"
}

 
En la salida se obtiene el código 200 OK:
 

{
	"user": {
		"id": 4,
		"email": "[email protected]",
		"role": "customer",
		"createdAt": "2023-03-28T21:32:16.237Z"
	},
	"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjQsInJvbGUiOiJjdXN0b21lciIsImlhdCI6MTY4MDAzOTE4M30.d49BvWGrgGtVcYHQEKTHS7CJeruE4mjTealUYgSwwS8"
}

 
Con ese token lo guardamos en Manage Environment, vamos ala carpeta Categories, luego a Get Categories, en la pestaña Auth, se selecciona Bearer y en TOKEN se coloca el token generado recientemente, al dar a Send, debe aparecer el código 200 OK con la lista de las Categorías almacenadas, en caso de que el cliente no tenga el role con privilegio, sale el código 401 Unauthorized.
 

Algo que me acabe de dar cuenta es que nostros podemos ejecutar cuantos middleware queramos , pasandolos por comas, pero esto hara que nuestro codigo sea mas desordenado. Por lo cual mi recomendacion es meterlos dentro de un arreglo para mantener el orden. ```js router.post('/', [ passport.authenticate('jwt', { session: false }), checkAdminRole, validatorHandler(createCategorySchema, 'body') ], async (req, res, next) => { try { const body = req.body; const newCategory = await service.create(body); res.status(201).json(newCategory); } catch (error) { next(error); } } ); ```router.post('/', \[ passport.authenticate('jwt', { session: false }), checkAdminRole, validatorHandler(createCategorySchema, 'body') ], async (req, res, next) => { try { const body = req.body; const newCategory = await service.create(body); res.status(201).json(newCategory); } catch (error) { next(error); } });
**AccessControl** **no estoy seguro si de esta manera será la mejor practica, pero a mí me funcionó XD** `//Permisos.js` `const AccessControl = require('accesscontrol');const ac = new AccessControl();` `ac.grant('customer')  .readOwn('profile')  .updateOwn('profile')  .createOwn('order')  .readOwn('order')  .updateOwn('order')  .deleteOwn('order')  .readAny('product')  .readAny('category');` `ac.grant('admin')  .extend('customer')  .createAny('product')  .updateAny('product')  .deleteAny('product')  .createAny('user')  .updateAny('user')  .deleteAny('user')  .createAny('customer')  .updateAny('customer')  .deleteAny('customer')  .createAny('category')  .updateAny('category')  .deleteAny('category');` `  module.exports = ac;` // auth.handler.js `const boom = require('@hapi/boom');const {config} = require('@api/config/config')const ac = require('@api/utils/roles/accessControl');` `function checkPermission(action, resource) {  return (req, res, next) => {    const role = req.user.role;    const permission = ac.can(role)[action](resource);    console.log(role,permission.granted,req.user)    if (permission.granted) {      next();     } else {      next(boom.unauthorized())    }  };}` `  module.exports={checkPermission}` // category.route.js `const express = require('express');const validatorHandler = require('@middlewares/validator.handler');const { checkPermission } = require('@middlewares/auth.handler');const passport = require('passport');const {  createCategorySchema,  updateCategorySchema,  getCategorySchema,} = require('./category.schema');const {  findAll,  findOne,  save,  update,  patch,  destroy,} = require('./category.controller');` `const router = express.Router();` `router.get('/', findAll);` `router.get('/:id', validatorHandler(getCategorySchema, 'params'), findOne);` `router.post(  '/',  passport.authenticate('jwt', { session: false }),  checkPermission('createAny', 'category'),  validatorHandler(createCategorySchema, 'body'),  save,);` `router.put(  '/:id',  passport.authenticate('jwt', { session: false }),  checkPermission('updateAny', 'category'),  validatorHandler(getCategorySchema, 'params'),  validatorHandler(updateCategorySchema, 'body'),  update,);router.patch(  '/:id',  passport.authenticate('jwt', { session: false }),  checkPermission('updateAny', 'category'),  validatorHandler(getCategorySchema, 'params'),  validatorHandler(updateCategorySchema, 'body'),  patch,);` `router.delete(  '/:id',  passport.authenticate('jwt', { session: false }),  checkPermission('deleteAny', 'category'),  validatorHandler(getCategorySchema, 'params'),  destroy,);` `module.exports = router;`
buena clase, pero me parece que la mayoria de estas validaciones deberían hacerse a nivel de base datos, casi todo lo quieren hacer en el servidor pero se olviden que existen otros componentes que pueden delegar estas actividades y cuidar el perfomance a nivel general.

Ni idea del por qué postman se cada cargando eternamente a pesar de que el servidor resuelva que es un 403 u.u