mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
fix(server): premissions guard for read and write endpoints
This commit is contained in:
67
packages/server/src/modules/Roles/Permission.guard.ts
Normal file
67
packages/server/src/modules/Roles/Permission.guard.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Request } from 'express';
|
||||
import { REQUIRED_PERMISSION_KEY, RequiredPermission } from './RequirePermission.decorator';
|
||||
import { AbilitySubject } from './Roles.types';
|
||||
|
||||
/**
|
||||
* Guard that checks if the user has the required permission to access a route.
|
||||
* Uses CASL ability instance attached to the request by AuthorizationGuard.
|
||||
*/
|
||||
@Injectable()
|
||||
export class PermissionGuard implements CanActivate {
|
||||
constructor(private readonly reflector: Reflector) {}
|
||||
|
||||
/**
|
||||
* Checks if the user has the required permission to access the route.
|
||||
* @param context - The execution context
|
||||
* @returns A boolean indicating if the user can access the route
|
||||
* @throws ForbiddenException if the user doesn't have the required permission
|
||||
*/
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredPermission = this.reflector.getAllAndOverride<RequiredPermission>(
|
||||
REQUIRED_PERMISSION_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
|
||||
// If no permission is required, allow access
|
||||
if (!requiredPermission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
const ability = (request as any).ability;
|
||||
|
||||
// If no ability instance is attached to the request, deny access
|
||||
if (!ability) {
|
||||
throw new ForbiddenException('Ability instance not found. Ensure AuthorizationGuard is applied.');
|
||||
}
|
||||
|
||||
const { ability: action, subject } = requiredPermission;
|
||||
|
||||
// Check if the user has the required permission using CASL ability
|
||||
const hasPermission = ability.can(action, subject);
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new ForbiddenException(
|
||||
`You do not have permission to ${action} ${subject}`,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if a subject value is a valid AbilitySubject.
|
||||
* @param subject - The subject value to check
|
||||
* @returns True if the subject is a valid AbilitySubject enum value
|
||||
*/
|
||||
private isValidSubject(subject: string): subject is AbilitySubject {
|
||||
return Object.values(AbilitySubject).includes(subject as AbilitySubject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { AbilitySubject } from './Roles.types';
|
||||
|
||||
export const REQUIRED_PERMISSION_KEY = 'requiredPermission';
|
||||
|
||||
export interface RequiredPermission {
|
||||
ability: string;
|
||||
subject: AbilitySubject | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator to specify required ability and subject for a route handler or controller.
|
||||
* @param ability - The ability/action required (e.g., 'Create', 'View', 'Edit', 'Delete')
|
||||
* @param subject - The subject/entity the ability applies to (e.g., AbilitySubject.Item, AbilitySubject.SaleInvoice)
|
||||
* @example
|
||||
* ```typescript
|
||||
* @RequirePermission('Create', AbilitySubject.Item)
|
||||
* @Post()
|
||||
* async createItem(@Body() dto: CreateItemDto) { ... }
|
||||
*
|
||||
* @RequirePermission('View', AbilitySubject.SaleInvoice)
|
||||
* @Get(':id')
|
||||
* async getInvoice(@Param('id') id: number) { ... }
|
||||
* ```
|
||||
*/
|
||||
export const RequirePermission = (
|
||||
ability: string,
|
||||
subject: AbilitySubject | string,
|
||||
) => SetMetadata(REQUIRED_PERMISSION_KEY, { ability, subject });
|
||||
@@ -10,6 +10,8 @@ import { RolePermission } from './models/RolePermission.model';
|
||||
import { RolesController } from './Roles.controller';
|
||||
import { RolesApplication } from './Roles.application';
|
||||
import { RolePermissionsSchema } from './queries/RolePermissionsSchema';
|
||||
import { AuthorizationGuard } from './Authorization.guard';
|
||||
import { PermissionGuard } from './Permission.guard';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(Role),
|
||||
@@ -25,9 +27,11 @@ const models = [
|
||||
GetRoleService,
|
||||
GetRolesService,
|
||||
RolesApplication,
|
||||
RolePermissionsSchema
|
||||
RolePermissionsSchema,
|
||||
AuthorizationGuard,
|
||||
PermissionGuard,
|
||||
],
|
||||
controllers: [RolesController],
|
||||
exports: [...models],
|
||||
exports: [...models, AuthorizationGuard, PermissionGuard],
|
||||
})
|
||||
export class RolesModule {}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IsOptional } from '@/common/decorators/Validators';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
@@ -41,7 +42,7 @@ export class CommandRolePermissionDto {
|
||||
export class CreateRolePermissionDto extends CommandRolePermissionDto { }
|
||||
export class EditRolePermissionDto extends CommandRolePermissionDto {
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'The permission ID',
|
||||
@@ -59,7 +60,6 @@ class CommandRoleDto {
|
||||
roleName: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({
|
||||
example: 'Administrator',
|
||||
description: 'The description of the role',
|
||||
@@ -71,9 +71,9 @@ export class CreateRoleDto extends CommandRoleDto {
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CommandRolePermissionDto)
|
||||
@Type(() => CreateRolePermissionDto)
|
||||
@ApiProperty({
|
||||
type: [CommandRolePermissionDto],
|
||||
type: [CreateRolePermissionDto],
|
||||
description: 'The permissions of the role',
|
||||
})
|
||||
permissions: Array<CreateRolePermissionDto>;
|
||||
|
||||
Reference in New Issue
Block a user