From 16ed5b33fc8343d76108f57525a7970d7fb7a6cd Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 5 May 2024 21:32:31 +0300 Subject: [PATCH] Add user device permission guards --- src/guards/device.permission.guard.ts | 97 ------------------- ...er.device.controllable.permission.guard.ts | 92 ++++++++++++++++++ src/guards/user.device.permission.guard.ts | 91 +++++++++++++++++ 3 files changed, 183 insertions(+), 97 deletions(-) delete mode 100644 src/guards/device.permission.guard.ts create mode 100644 src/guards/user.device.controllable.permission.guard.ts create mode 100644 src/guards/user.device.permission.guard.ts diff --git a/src/guards/device.permission.guard.ts b/src/guards/device.permission.guard.ts deleted file mode 100644 index 1ec21a9..0000000 --- a/src/guards/device.permission.guard.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { PermissionType } from '@app/common/constants/permission-type.enum'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device-user-permission/repositories'; -import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; -import { - Injectable, - CanActivate, - HttpStatus, - ExecutionContext, - BadRequestException, - applyDecorators, - SetMetadata, - UseGuards, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; - -@Injectable() -export class DevicePermissionGuard implements CanActivate { - constructor( - private reflector: Reflector, - private readonly deviceUserPermissionRepository: DeviceUserPermissionRepository, - private readonly permissionTypeRepository: PermissionTypeRepository, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - try { - const { deviceId } = req.headers; - const userId = req.user.uuid; - - const requirePermission = - this.reflector.getAllAndOverride('permission', [ - context.getHandler(), - context.getClass(), - ]); - - if (!requirePermission) { - return true; - } - await this.checkDevicePermission(deviceId, userId, requirePermission); - - return true; - } catch (error) { - this.handleGuardError(error, context); - return false; - } - } - - async checkDevicePermission( - deviceId: string, - userId: string, - requirePermission, - ) { - const [userPermissionDetails, permissionDetails] = await Promise.all([ - this.deviceUserPermissionRepository.findOne({ - where: { deviceUuid: deviceId, userUuid: userId }, - }), - this.permissionTypeRepository.findOne({ - where: { - type: requirePermission, - }, - }), - ]); - if (!userPermissionDetails) { - throw new BadRequestException('User Permission Details Not Found'); - } - if (userPermissionDetails.permissionType.uuid !== permissionDetails.uuid) { - throw new BadRequestException( - `User Does not have a ${requirePermission}`, - ); - } - } - - private handleGuardError(error: Error, context: ExecutionContext) { - const response = context.switchToHttp().getResponse(); - console.error(error); - - if (error instanceof BadRequestException) { - response - .status(HttpStatus.BAD_REQUEST) - .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); - } else { - response.status(HttpStatus.NOT_FOUND).json({ - statusCode: HttpStatus.NOT_FOUND, - message: 'User Permission not found', - }); - } - } -} - -export function AuthGuardWithRoles(permission?: string) { - return applyDecorators( - SetMetadata('permission', permission), - UseGuards(JwtAuthGuard), - UseGuards(DevicePermissionGuard), - ); -} diff --git a/src/guards/user.device.controllable.permission.guard.ts b/src/guards/user.device.controllable.permission.guard.ts new file mode 100644 index 0000000..2684c8d --- /dev/null +++ b/src/guards/user.device.controllable.permission.guard.ts @@ -0,0 +1,92 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { PermissionType } from '@app/common/constants/permission-type.enum'; + +@Injectable() +export class CheckUserHaveControllablePermission implements CanActivate { + constructor( + private readonly deviceRepository: DeviceRepository, + private readonly userRepository: UserRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const userUuid = req.user.uuid; + const { deviceUuid } = req.params; + + const userIsFound = await this.checkUserIsFound(userUuid); + if (!userIsFound) { + throw new NotFoundException('User not found'); + } + + const userDevicePermission = await this.checkUserDevicePermission( + userUuid, + deviceUuid, + ); + + if (userDevicePermission === PermissionType.CONTROLLABLE) { + return true; + } else { + throw new BadRequestException( + 'You do not have controllable access to this device', + ); + } + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkUserIsFound(userUuid: string) { + const userData = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + return !!userData; + } + + private async checkUserDevicePermission( + userUuid: string, + deviceUuid: string, + ): Promise { + const device = await this.deviceRepository.findOne({ + where: { uuid: deviceUuid, permission: { userUuid: userUuid } }, + relations: ['permission', 'permission.permissionType'], + }); + + if (!device) { + throw new BadRequestException( + 'You do not have controllable access to this device', + ); + } + + return device.permission[0].permissionType.type; // Assuming permissionType is a string + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if (error instanceof NotFoundException) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else if (error instanceof BadRequestException) { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: error.message, + }); + } else { + response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + message: error.message, + }); + } + } +} diff --git a/src/guards/user.device.permission.guard.ts b/src/guards/user.device.permission.guard.ts new file mode 100644 index 0000000..e63a08e --- /dev/null +++ b/src/guards/user.device.permission.guard.ts @@ -0,0 +1,91 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + HttpStatus, +} from '@nestjs/common'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { PermissionType } from '@app/common/constants/permission-type.enum'; + +@Injectable() +export class CheckUserHavePermission implements CanActivate { + constructor( + private readonly deviceRepository: DeviceRepository, + private readonly userRepository: UserRepository, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + + try { + const userUuid = req.user.uuid; + const { deviceUuid } = req.params; + + const userIsFound = await this.checkUserIsFound(userUuid); + if (!userIsFound) { + throw new NotFoundException('User not found'); + } + + const userDevicePermission = await this.checkUserDevicePermission( + userUuid, + deviceUuid, + ); + + if ( + userDevicePermission === PermissionType.READ || + userDevicePermission === PermissionType.CONTROLLABLE + ) { + return true; + } else { + throw new BadRequestException('You do not have access to this device'); + } + } catch (error) { + this.handleGuardError(error, context); + return false; + } + } + + private async checkUserIsFound(userUuid: string) { + const userData = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + return !!userData; + } + + private async checkUserDevicePermission( + userUuid: string, + deviceUuid: string, + ): Promise { + const device = await this.deviceRepository.findOne({ + where: { uuid: deviceUuid, permission: { userUuid: userUuid } }, + relations: ['permission', 'permission.permissionType'], + }); + + if (!device) { + throw new BadRequestException('You do not have access to this device'); + } + + return device.permission[0].permissionType.type; // Assuming permissionType is a string + } + + private handleGuardError(error: Error, context: ExecutionContext) { + const response = context.switchToHttp().getResponse(); + if (error instanceof NotFoundException) { + response + .status(HttpStatus.NOT_FOUND) + .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); + } else if (error instanceof BadRequestException) { + response.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: error.message, + }); + } else { + response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + message: error.message, + }); + } + } +}