Integración con NestJS
En esta sección, aprenderás cómo configurar y utilizar @casl/ability en un proyecto NestJS para gestionar permisos y autorizaciones basados en roles y permisos almacenados en una base de datos MongoDB
Paso 1: Instalación de dependencias
Primero, instala las dependencias necesarias para @casl/ability
y Mongoose.
npm install @casl/ability @nestjs/casl @nestjs/mongoose mongoose
Paso 2: Configuración de Mongoose
Configura Mongoose en tu módulo principal (app.module.ts
).
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UserModule } from './user/user.module';
import { AbilityModule } from './ability/ability.module';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/nest'),
UserModule,
AbilityModule,
],
})
export class AppModule {}
Paso 3: Definición de Esquemas y Modelos
Crea los esquemas y modelos para rol
, permission
, y rol_permission
.
Role Schema:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { Permission } from './permission.schema';
@Schema()
export class Role extends Document {
@Prop({ required: true })
name: string;
@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Permission' }] })
permissions: Permission[];
}
export const RoleSchema = SchemaFactory.createForClass(Role);
Permission Schema:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema()
export class Permission extends Document {
@Prop({ required: true })
codename: string;
@Prop({ required: true })
section: string;
@Prop({ required: true })
name: string;
@Prop({ required: true })
action: string;
@Prop({ required: true })
description: string;
}
export const PermissionSchema = SchemaFactory.createForClass(Permission);
Paso 4: Servicios para Obtener Roles y Permisos
Crea un servicio para obtener los roles y permisos del usuario.
Role Service:
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Role } from '../schemas/role.schema';
@Injectable()
export class RoleService {
constructor(@InjectModel(Role.name) private roleModel: Model<Role>) {}
async getRolesForUser(rolUserId: string): Promise<Role[]> {
return this.roleModel.find({ rolUserId }).populate('permissions').exec();
}
}
Paso 5: Definición de Habilidades
Define las habilidades basadas en los roles y permisos.
Ability Factory:
import {
Ability,
AbilityBuilder,
AbilityClass,
ExtractSubjectType,
InferSubjects
} from '@casl/ability';
import { RoleService } from '../services/role.service';
import { User } from '../schemas/user.schema';
type Actions = 'manage' | 'create' | 'read' | 'update' | 'delete';
type Subjects = InferSubjects<any> | 'all';
export type AppAbility = Ability<[Actions, Subjects]>;
export async function defineAbilityFor(user: User, roleService: RoleService) {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(
Ability as AbilityClass<AppAbility>
);
const roles = await roleService.getRolesForUser(user.role_id);
roles.forEach(role => {
role.permissions.forEach(permission => {
can(permission.action, permission.section);
});
});
return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}
Paso 6: Middleware para Inyectar Habilidades
Inyecta las habilidades en el contexto de la solicitud.
Ability Middleware:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { RoleService } from '../services/role.service';
import { defineAbilityFor } from './ability.factory';
@Injectable()
export class AbilityMiddleware implements NestMiddleware {
constructor(private roleService: RoleService) {}
async use(req: Request, res: Response, next: NextFunction) {
const user = req.user; // Asegúrate de que el usuario esté presente en la solicitud
req.ability = await defineAbilityFor(user, this.roleService);
next();
}
}
Paso 7: Uso de CASL en Controladores
Utiliza los guards y decoradores en tus controladores.
Abilities Guard:
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ForbiddenError } from '@casl/ability';
import { CHECK_ABILITY, RequiredRule } from './ability.decorator';
import { AppAbility } from './ability.factory';
@Injectable()
export class AbilitiesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const rules = this.reflector.get<RequiredRule[]>(CHECK_ABILITY, context.getHandler()) || [];
const { user, ability } = context.switchToHttp().getRequest();
try {
rules.forEach(rule =>
ForbiddenError.from(ability).throwUnlessCan(rule.action, rule.subject)
);
return true;
} catch (error) {
if (error instanceof ForbiddenError) {
throw new ForbiddenException(error.message);
}
return false;
}
}
}
Ability Decorator:
import { SetMetadata } from '@nestjs/common';
export const CHECK_ABILITY = 'check_ability';
export interface RequiredRule {
action: string;
subject: string;
}
export const CheckAbilities = (...rules: RequiredRule[]) =>
SetMetadata(CHECK_ABILITY, rules);
Ejemplo de Controlador:
import { Controller, Get, Post, Param, UseGuards } from '@nestjs/common';
import { AbilitiesGuard } from './abilities.guard';
import { CheckAbilities } from './ability.decorator';
import { Action } from './ability.factory';
import { Article } from '../articles/article.entity';
@Controller('articles')
@UseGuards(AbilitiesGuard)
export class ArticlesController {
@Get(':id')
@CheckAbilities({ action: 'read', subject: Article })
findOne(@Param('id') id: string) {
// lógica para encontrar un artículo
}
@Post()
@CheckAbilities({ action: 'create', subject: Article })
create() {
// lógica para crear un artículo
}
}
Last updated