-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Eventクラスの作成、各種Guardの作成などによるRoomモジュールのリファクタ (#143)
* [backend] Add kick guard and refactor room controller * [backend] Add ChangeRoleGuard and refactor updateUserOnRoom * [backend] Add OwnerGuard and refactor room.controller.ts * [backend] Refactor room controller to have controller level guards
- Loading branch information
Showing
10 changed files
with
281 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export class RoomLeftEvent { | ||
roomId: number; | ||
userId: number; | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { | ||
Injectable, | ||
CanActivate, | ||
ExecutionContext, | ||
BadRequestException, | ||
ForbiddenException, | ||
} from '@nestjs/common'; | ||
import { Role } from '@prisma/client'; | ||
import { PrismaService } from 'src/prisma/prisma.service'; | ||
import { UpdateUserOnRoomDto } from '../dto/update-UserOnRoom.dto'; | ||
|
||
@Injectable() | ||
export class ChangeRoleGuard implements CanActivate { | ||
constructor(private prisma: PrismaService) {} | ||
|
||
expectNumberParam( | ||
param: string | null | undefined, | ||
paramName: string, | ||
): number { | ||
if (!param) { | ||
throw new BadRequestException(`${paramName} is required`); | ||
} | ||
if (!/^\d+$/.test(param)) { | ||
throw new BadRequestException(`${paramName} must be a number`); | ||
} | ||
return Number(param); | ||
} | ||
|
||
async canActivate(context: ExecutionContext) { | ||
const req = context.switchToHttp().getRequest(); | ||
const { params, member } = req; | ||
if (!member) { | ||
throw new ForbiddenException('require member'); | ||
} | ||
// Validate roomId and targetUserId(userId) | ||
const roomId = this.expectNumberParam(params.roomId, 'roomId'); | ||
const targetUserId = this.expectNumberParam(params.userId, 'userId'); | ||
|
||
// Check if targetUser is a member of the room | ||
const userOnRoom = await this.prisma.userOnRoom.findUniqueOrThrow({ | ||
where: { | ||
userId_roomId_unique: { | ||
userId: targetUserId, | ||
roomId: roomId, | ||
}, | ||
}, | ||
}); | ||
const targetRole = userOnRoom.role; | ||
|
||
// If member is trying to change someone else's role throw a ForbiddenException | ||
if (member.role === Role.MEMBER) { | ||
throw new ForbiddenException('Members cannot change the role of others'); | ||
} | ||
|
||
// If admin is trying to kick owner, throw a ForbiddenException | ||
if (targetRole === Role.OWNER && member.role === Role.ADMINISTRATOR) { | ||
throw new ForbiddenException('Admins cannot change the role of owner'); | ||
} | ||
|
||
// Cannot change the role to be owner | ||
// Ownership should be transferred by owner | ||
const dto: UpdateUserOnRoomDto = req.body; | ||
if (dto.role === Role.OWNER) { | ||
throw new ForbiddenException('Anyone cannot change the role to be owner'); | ||
} | ||
|
||
// Otherwise, admin/owner kicking someone else, that's ok | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { | ||
Injectable, | ||
CanActivate, | ||
ExecutionContext, | ||
BadRequestException, | ||
ForbiddenException, | ||
} from '@nestjs/common'; | ||
import { Role } from '@prisma/client'; | ||
import { PrismaService } from 'src/prisma/prisma.service'; | ||
|
||
@Injectable() | ||
export class KickGuard implements CanActivate { | ||
constructor(private prisma: PrismaService) {} | ||
|
||
expectNumberParam( | ||
param: string | null | undefined, | ||
paramName: string, | ||
): number { | ||
if (!param) { | ||
throw new BadRequestException(`${paramName} is required`); | ||
} | ||
if (!/^\d+$/.test(param)) { | ||
throw new BadRequestException(`${paramName} must be a number`); | ||
} | ||
return Number(param); | ||
} | ||
|
||
async canActivate(context: ExecutionContext) { | ||
const req = context.switchToHttp().getRequest(); | ||
const { params, user, member } = req; | ||
if (!user || !member) { | ||
throw new ForbiddenException('require login and member'); | ||
} | ||
// Validate roomId and targetUserId(userId) | ||
const roomId = this.expectNumberParam(params.roomId, 'roomId'); | ||
const targetUserId = this.expectNumberParam(params.userId, 'userId'); | ||
|
||
// Check if targetUser is a member of the room | ||
// If target user is not found, it's okay to return NotFoundException, | ||
// So I don't want to implement any try/catch here. | ||
const userOnRoom = await this.prisma.userOnRoom.findUniqueOrThrow({ | ||
where: { | ||
userId_roomId_unique: { | ||
userId: targetUserId, | ||
roomId: roomId, | ||
}, | ||
}, | ||
}); | ||
const targetRole = userOnRoom.role; | ||
|
||
// If anyone is trying to kick themself, that's ok | ||
if (targetUserId === user.id) { | ||
return true; | ||
} | ||
|
||
// If member is trying to kick someone else throw a ForbiddenException | ||
if (member.role === Role.MEMBER) { | ||
throw new ForbiddenException('Members cannot kick others'); | ||
} | ||
|
||
// If admin is trying to kick owner, throw a ForbiddenException | ||
if (targetRole === Role.OWNER && member.role === Role.ADMINISTRATOR) { | ||
throw new ForbiddenException('Admins cannot kick owners'); | ||
} | ||
|
||
// Otherwise, admin/owner kicking someone else, that's ok | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { | ||
Injectable, | ||
CanActivate, | ||
ExecutionContext, | ||
BadRequestException, | ||
ForbiddenException, | ||
} from '@nestjs/common'; | ||
import { Role } from '@prisma/client'; | ||
import { PrismaService } from 'src/prisma/prisma.service'; | ||
|
||
@Injectable() | ||
export class OwnerGuard implements CanActivate { | ||
constructor(private prisma: PrismaService) {} | ||
|
||
expectNumberParam( | ||
param: string | null | undefined, | ||
paramName: string, | ||
): number { | ||
if (!param) { | ||
throw new BadRequestException(`${paramName} is required`); | ||
} | ||
if (!/^\d+$/.test(param)) { | ||
throw new BadRequestException(`${paramName} must be a number`); | ||
} | ||
return Number(param); | ||
} | ||
|
||
async canActivate(context: ExecutionContext) { | ||
const req = context.switchToHttp().getRequest(); | ||
const { params, user } = req; | ||
// Validate roomId and targetUserId(userId) | ||
const roomId = this.expectNumberParam(params.roomId, 'roomId'); | ||
if (!user) { | ||
throw new ForbiddenException('require login'); | ||
} | ||
|
||
// Check if targetUser is a member of the room | ||
let userOnRoom; | ||
try { | ||
userOnRoom = await this.prisma.userOnRoom.findUniqueOrThrow({ | ||
where: { | ||
userId_roomId_unique: { | ||
userId: user.id, | ||
roomId: roomId, | ||
}, | ||
}, | ||
}); | ||
} catch (e) { | ||
if (e.code === 'P2025') { | ||
throw new ForbiddenException('User not found in the room'); | ||
} else { | ||
throw e; | ||
} | ||
} | ||
|
||
// Check if user is the owner | ||
if (userOnRoom.role !== Role.OWNER) { | ||
throw new ForbiddenException('Only owner can do this'); | ||
} | ||
req.member = userOnRoom; | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.