Skip to content

Commit

Permalink
Feat/backend/room/add role guard (#127)
Browse files Browse the repository at this point in the history
add RoomRolesGuard
  • Loading branch information
kotto5 authored Dec 10, 2023
1 parent 7800e04 commit 6ec9486
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 107 deletions.
56 changes: 56 additions & 0 deletions backend/src/room/room-member.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { RoomService } from './room.service';
import { Role } from '@prisma/client';

interface User {
id: number;
name: string;
}

@Injectable()
export class RoomRolesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private roomService: RoomService,
) {}

canActivate(
context: ExecutionContext,
): Promise<boolean> | boolean | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const user = request['user'];
const roomId = request.params.id;
return this.getUserRole(user, roomId)
.then((userRole) => {
request['userRole'] = userRole;
return true;
})
.catch(() => {
return false;
});
}

private getUserRole(user: User, roomId: string): Promise<Role> {
return this.roomService
.findUserOnRoom(Number(roomId), user.id)
.then((userOnRoomEntity) => userOnRoomEntity.role)
.catch(() => Promise.reject());
}

private meetRequirement(need: Role, userRole: Role): boolean {
return this.roleToNum(userRole) >= this.roleToNum(need);
}

private roleToNum(role: Role): number {
switch (role) {
case Role.MEMBER:
return 0;
case Role.ADMINISTRATOR:
return 1;
case Role.OWNER:
return 2;
}
}
}
35 changes: 17 additions & 18 deletions backend/src/room/room.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { RoomEntity } from './entities/room.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';
import { UserOnRoomEntity } from './entities/UserOnRoom.entity';
import { UpdateUserOnRoomDto } from './dto/update-UserOnRoom.dto';
import { RoomRolesGuard } from './room-member.guard';

@Controller('room')
@ApiTags('room')
Expand All @@ -45,35 +46,32 @@ export class RoomController {
}

@Get(':id')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RoomRolesGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: RoomEntity })
findOne(@Param('id', ParseIntPipe) id: number, @Req() request: Request) {
return this.roomService.findRoom(id, request['user']);
findOne(@Param('id', ParseIntPipe) id: number) {
return this.roomService.findRoom(id);
}

@Patch(':id')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RoomRolesGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: RoomEntity })
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateRoomDto: UpdateRoomDto,
@Req() request: Request,
) {
return this.roomService.updateRoom(id, updateRoomDto, request['user']);
return this.roomService.updateRoom(id, updateRoomDto, request['userRole']);
}

@Delete(':id')
@HttpCode(204)
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RoomRolesGuard)
@ApiBearerAuth()
@ApiNoContentResponse()
async removeRoom(
@Param('id', ParseIntPipe) id: number,
@Req() request: Request,
) {
await this.roomService.removeRoom(id, request['user']);
removeRoom(@Param('id', ParseIntPipe) id: number, @Req() request: Request) {
return this.roomService.removeRoom(id, request['userRole']);
}

@Post(':id')
Expand All @@ -88,35 +86,36 @@ export class RoomController {
}

@Get(':id/:userId')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RoomRolesGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: UserOnRoomEntity })
getUserOnRoom(
@Param('id', ParseIntPipe) id: number,
@Param('userId', ParseIntPipe) userId: number,
@Req() request: Request,
) {
return this.roomService.findUserOnRoom(id, request['user'], userId);
return this.roomService.findUserOnRoom(id, userId);
}

@Delete(':id/:userId')
@HttpCode(204)
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RoomRolesGuard)
@ApiBearerAuth()
@ApiNoContentResponse()
async deleteUserOnRoom(
@Param('id', ParseIntPipe) id: number,
@Param('userId', ParseIntPipe) userId: number,
@Req() request: Request,
) {
await this.roomService.removeUserOnRoom(
id,
{ id: userId, name: 'test' },
request['userRole'],
userId,
request['user'],
);
}

@Patch(':id/:userId')
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RoomRolesGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: UserOnRoomEntity })
updateUserOnRoom(
Expand All @@ -127,7 +126,7 @@ export class RoomController {
) {
return this.roomService.updateUserOnRoom(
id,
request['user'],
request['userRole'],
userId,
updateUserOnRoomDto,
);
Expand Down
170 changes: 91 additions & 79 deletions backend/src/room/room.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,60 +42,35 @@ export class RoomService {
return this.prisma.room.findMany();
}

findRoom(id: number, user: User): Promise<RoomEntity> {
return this.prisma.room
.findUniqueOrThrow({
where: { id },
include: {
users: true,
},
})
.then((roomEntity) => {
const userOnRoomEntity = roomEntity.users.find(
(userOnRoomEntity) => userOnRoomEntity.userId === user.id,
);
if (userOnRoomEntity === undefined) {
throw new HttpException('Forbidden', 403);
} else {
return roomEntity;
}
});
findRoom(id: number): Promise<RoomEntity> {
return this.prisma.room.findUniqueOrThrow({
where: { id },
include: {
users: true,
},
});
}

updateRoom(
updateRoom = (
id: number,
updateRoomDto: UpdateRoomDto,
user: User,
): Promise<RoomEntity> {
return this.findUserOnRoom(id, user, user.id).then((userOnRoomEntity) => {
if (userOnRoomEntity.role !== Role.OWNER) {
throw new HttpException('Forbidden', 403);
} else {
return this.prisma.room.update({
role: Role,
): Promise<RoomEntity> =>
role !== Role.OWNER
? Promise.reject(new HttpException('Forbidden', 403))
: this.prisma.room.update({
where: { id },
data: updateRoomDto,
});
}
});
}

removeRoom(id: number, user: User): Promise<RoomEntity> {
return this.findUserOnRoom(id, user, user.id)
.catch(() => {
throw new HttpException('Forbidden', 403);
})
.then((userOnRoomEntity) => {
if (userOnRoomEntity.role !== Role.OWNER) {
throw new HttpException('Forbidden', 403);
} else {
return this.removeAllUserOnRoom(id).then(() =>
this.prisma.room.delete({
where: { id },
}),
);
}
});
}
removeRoom = (id: number, role: Role): Promise<RoomEntity> =>
role !== Role.OWNER
? Promise.reject(new HttpException('Forbidden', 403))
: this.removeAllUserOnRoom(id).then(() =>
this.prisma.room.delete({
where: { id },
}),
);

// UserOnRoom CRUD

Expand All @@ -117,7 +92,6 @@ export class RoomService {

findUserOnRoom = (
roomId: number,
client: User,
userId: number,
): Promise<UserOnRoomEntity> => {
return this.prisma.userOnRoom.findUniqueOrThrow({
Expand All @@ -130,51 +104,89 @@ export class RoomService {
});
};

updateUserOnRoom(
updateUserOnRoom = (
roomId: number,
client: User,
role: Role,
userId: number,
updateUserOnRoom: UpdateUserOnRoomDto,
): Promise<UserOnRoomEntity> {
return this.prisma.userOnRoom.update({
where: {
userId_roomId_unique: {
roomId: roomId,
userId: userId,
},
},
data: updateUserOnRoom,
});
}
dto: UpdateUserOnRoomDto,
): Promise<UserOnRoomEntity> => {
if (role === Role.MEMBER)
return Promise.reject(new HttpException('Forbidden', 403));
const validateRole =
(changerRole: Role) =>
(targetRole: Role): boolean =>
this.roleToNum(changerRole) >= this.roleToNum(targetRole);
const validateRoleBy = validateRole(role);

if (this.roleToNum(dto.role) !== -1 && validateRoleBy(dto.role) === false)
return Promise.reject(new HttpException('Forbidden', 403));
else
return this.findUserOnRoom(roomId, userId)
.then((userOnRoomEntity) =>
validateRoleBy(userOnRoomEntity.role) === false
? Promise.reject(new HttpException('Forbidden', 403))
: this.prisma.userOnRoom.update({
where: {
userId_roomId_unique: {
roomId: roomId,
userId: userId,
},
},
data: dto,
}),
)
.catch((err) => {
throw err;
});
};

removeUserOnRoom(
roomId: number,
client: User,
role: Role,
userId: number,
client: User,
): Promise<UserOnRoomEntity> {
return this.findUserOnRoom(roomId, client, client.id)
.then((userOnRoomEntity) => {
if (userOnRoomEntity.role === Role.OWNER || client.id === userId) {
return this.prisma.userOnRoom.delete({
where: {
userId_roomId_unique: {
roomId: roomId,
userId: userId,
},
if (client.id != userId && role === Role.MEMBER)
return Promise.reject(new HttpException('Forbidden', 403));
const validateRole =
(changerRole: Role) =>
(targetRole: Role): boolean => {
return this.roleToNum(changerRole) >= this.roleToNum(targetRole);
};
const validateRoleBy = validateRole(role);

return this.findUserOnRoom(roomId, userId).then((userOnRoomEntity) => {
if (validateRoleBy(userOnRoomEntity.role)) {
return this.prisma.userOnRoom.delete({
where: {
userId_roomId_unique: {
roomId: roomId,
userId: userId,
},
});
} else {
throw 404;
}
})
.catch((err) => {
throw err;
});
},
});
} else {
return Promise.reject(new HttpException('Forbidden', 403));
}
});
}

removeAllUserOnRoom(roomId: number): Promise<BatchPayload> {
return this.prisma.userOnRoom.deleteMany({
where: { roomId },
});
}

private roleToNum(role: Role): number {
switch (role) {
case Role.MEMBER:
return 0;
case Role.ADMINISTRATOR:
return 1;
case Role.OWNER:
return 2;
default:
return -1;
}
}
}
Loading

0 comments on commit 6ec9486

Please sign in to comment.