Add user device permission guards

This commit is contained in:
faris Aljohari
2024-05-05 21:32:31 +03:00
parent 6019e92c5d
commit 16ed5b33fc
3 changed files with 183 additions and 97 deletions

View File

@ -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<boolean> {
const req = context.switchToHttp().getRequest();
try {
const { deviceId } = req.headers;
const userId = req.user.uuid;
const requirePermission =
this.reflector.getAllAndOverride<PermissionType>('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),
);
}

View File

@ -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<boolean> {
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<string> {
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,
});
}
}
}

View File

@ -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<boolean> {
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<string> {
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,
});
}
}
}