Skip to content

Commit 299da5e

Browse files
committed
feat: user ip ban 로직 추가
1 parent e94af4c commit 299da5e

File tree

10 files changed

+174
-60
lines changed

10 files changed

+174
-60
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { HttpStatus } from '@nestjs/common';
2+
import { WsException } from '@nestjs/websockets';
3+
4+
class ChatException extends WsException {
5+
statusCode: number;
6+
constructor({ statusCode, message } : ChatError , public roomId?: string) {
7+
super({ statusCode, message, roomId });
8+
this.statusCode = statusCode;
9+
}
10+
11+
getError(): object {
12+
return {
13+
statusCode: this.statusCode,
14+
msg: this.message,
15+
roomId: this.roomId || null,
16+
};
17+
}
18+
}
19+
20+
interface ChatError {
21+
statusCode: number;
22+
message: string;
23+
}
24+
25+
const CHATTING_SOCKET_ERROR = {
26+
ROOM_EMPTY: {
27+
statusCode: HttpStatus.BAD_REQUEST,
28+
message: '유저가 참여하고 있는 채팅방이 없습니다.'
29+
},
30+
31+
ROOM_EXISTED: {
32+
statusCode: HttpStatus.BAD_REQUEST,
33+
message: '이미 존재하는 방입니다.'
34+
},
35+
36+
INVALID_USER: {
37+
statusCode: HttpStatus.UNAUTHORIZED,
38+
message: '유효하지 않는 유저입니다.'
39+
},
40+
41+
QUESTION_EMPTY: {
42+
statusCode: HttpStatus.BAD_REQUEST,
43+
message: '유효하지 않은 질문입니다.'
44+
},
45+
46+
BAN_USER: {
47+
statusCode: HttpStatus.FORBIDDEN,
48+
message: '호스트에 의해 밴 당한 유저입니다.'
49+
}
50+
51+
52+
};
53+
export { CHATTING_SOCKET_ERROR, ChatException };
54+

backend/chatServer/src/chat/chat.gateway.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,29 @@ import {
66
OnGatewayConnection,
77
OnGatewayDisconnect,
88
MessageBody,
9-
ConnectedSocket,
9+
ConnectedSocket
1010
} from '@nestjs/websockets';
1111
import { UseGuards } from '@nestjs/common';
1212
import { Server, Socket } from 'socket.io';
1313
import {
14-
CHATTING_SOCKET_DEFAULT_EVENT,
15-
CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_SEND_EVENT
14+
CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_SEND_EVENT
1615
} from '../event/constants';
1716
import {
17+
BanUserIncomingMessageDto,
1818
NormalIncomingMessageDto, NoticeIncomingMessageDto, QuestionDoneIncomingMessageDto, QuestionIncomingMessageDto
1919
} from '../event/dto/IncomingMessage.dto';
2020
import { JoiningRoomDto } from '../event/dto/JoiningRoom.dto';
2121
import { RoomService } from '../room/room.service';
2222
import { createAdapter } from '@socket.io/redis-adapter';
23-
import { HostGuard, MessageGuard } from './chat.guard';
23+
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';
2424
import { LeavingRoomDto } from '../event/dto/LeavingRoom.dto';
2525
import {
2626
NormalOutgoingMessageDto,
2727
NoticeOutgoingMessageDto,
2828
QuestionOutgoingMessageDto
2929
} from '../event/dto/OutgoingMessage.dto';
3030
import { QuestionDto } from '../event/dto/Question.dto';
31+
import { ChatException, CHATTING_SOCKET_ERROR } from './chat.error';
3132

3233
@WebSocketGateway({ cors: true })
3334
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
@@ -69,6 +70,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
6970
}
7071

7172
// 특정 방에 참여하기 위한 메서드
73+
@UseGuards(BlacklistGuard)
7274
@SubscribeMessage(CHATTING_SOCKET_DEFAULT_EVENT.JOIN_ROOM)
7375
async handleJoinRoom(client: Socket, payload: JoiningRoomDto) {
7476
const { roomId, userId } = payload;
@@ -93,7 +95,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
9395
}
9496

9597
// 방에 NORMAL 메시지를 보내기 위한 메서드
96-
@UseGuards(MessageGuard)
98+
@UseGuards(MessageGuard, BlacklistGuard)
9799
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.NORMAL)
98100
async handleNormalMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: NormalIncomingMessageDto) {
99101
const { roomId, userId, msg } = payload;
@@ -103,7 +105,8 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
103105
...user,
104106
msg,
105107
msgTime: new Date().toISOString(),
106-
msgType: 'normal'
108+
msgType: 'normal',
109+
socketId: client.id
107110
};
108111
console.log('Normal Message Come In: ', normalOutgoingMessage);
109112
const hostId = await this.roomService.getHostOfRoom(roomId);
@@ -121,7 +124,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
121124
}
122125

123126
// 방에 QUESTION 메시지를 보내기 위한 메서드
124-
@UseGuards(MessageGuard)
127+
@UseGuards(MessageGuard,BlacklistGuard)
125128
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.QUESTION)
126129
async handleQuestionMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: QuestionIncomingMessageDto) {
127130
const { roomId, msg } = payload;
@@ -132,7 +135,8 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
132135
msg,
133136
msgTime: new Date().toISOString(),
134137
msgType: 'question',
135-
questionDone: false
138+
questionDone: false,
139+
socketId: client.id
136140
};
137141

138142
const question: QuestionOutgoingMessageDto = await this.roomService.addQuestion(roomId, questionWithoutId);
@@ -150,8 +154,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
150154
}
151155

152156
// 방에 NOTICE 메시지를 보내기 위한 메서드
153-
@UseGuards(MessageGuard)
154-
@UseGuards(HostGuard)
157+
@UseGuards(MessageGuard, HostGuard)
155158
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.NOTICE)
156159
async handleNoticeMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: NoticeIncomingMessageDto) {
157160
const { roomId, msg } = payload;
@@ -165,4 +168,20 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
165168
};
166169
this.server.to(roomId).emit(CHATTING_SOCKET_RECEIVE_EVENT.NOTICE, noticeOutgoingMessage);
167170
}
171+
172+
@UseGuards(HostGuard)
173+
@SubscribeMessage(CHATTING_SOCKET_DEFAULT_EVENT.BAN_USER)
174+
async handleBanUserMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: BanUserIncomingMessageDto) {
175+
const { roomId, socketId } = payload;
176+
const banUser = this.server.sockets.sockets.get(socketId);
177+
const address = banUser?.handshake.address.replaceAll('::ffff:', '');
178+
179+
if(!address) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER);
180+
181+
const forwarded = banUser?.handshake.headers.forwarded ?? address;
182+
console.log('ban:', roomId, address, forwarded);
183+
184+
await this.roomService.addUserToBlacklist(roomId, address, forwarded);
185+
console.log(await this.roomService.getUserBlacklist(roomId, address));
186+
}
168187
}

backend/chatServer/src/chat/chat.guard.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
22
import { RoomService } from '../room/room.service';
3+
import { Socket } from 'socket.io';
4+
5+
import { ChatException, CHATTING_SOCKET_ERROR } from './chat.error';
36

47
@Injectable()
58
export class MessageGuard implements CanActivate {
@@ -22,3 +25,30 @@ export class HostGuard implements CanActivate {
2225
return hostId === userId;
2326
}
2427
}
28+
29+
@Injectable()
30+
export class BlacklistGuard implements CanActivate {
31+
constructor(private roomService: RoomService) {};
32+
async canActivate(context: ExecutionContext) {
33+
const payload = context.switchToWs().getData();
34+
const { roomId } = payload;
35+
36+
const client: Socket = context.switchToWs().getClient<Socket>();
37+
const address = client.handshake.address.replaceAll('::ffff:', '');
38+
const forwarded = client.handshake.headers.forwarded?.split(',')[0] ?? address;
39+
40+
const isValidUser = await this.whenJoinRoom(roomId, address, forwarded);
41+
42+
if(!isValidUser) throw new ChatException(CHATTING_SOCKET_ERROR.BAN_USER, roomId);
43+
return true;
44+
}
45+
46+
async whenJoinRoom(roomId: string, address: string, forwarded: string) {
47+
console.log(roomId, address, forwarded);
48+
const blacklistInRoom = await this.roomService.getUserBlacklist(roomId, address);
49+
console.log(blacklistInRoom);
50+
const isInBlacklistUser = blacklistInRoom.some((blackForwarded) => blackForwarded === forwarded);
51+
console.log('blacklistInRoom:', isInBlacklistUser);
52+
return !isInBlacklistUser;
53+
}
54+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Module } from '@nestjs/common';
22
import { ChatGateway } from './chat.gateway';
33
import { RoomModule } from '../room/room.module';
4-
import { MessageGuard } from './chat.guard';
4+
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';
55

66
@Module({
77
imports: [RoomModule],
8-
providers: [ChatGateway, MessageGuard],
8+
providers: [ChatGateway, MessageGuard, BlacklistGuard, HostGuard],
99
})
1010
export class ChatModule {}
Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { HttpStatus } from '@nestjs/common';
2-
31
const CHATTING_SOCKET_DEFAULT_EVENT = {
42
JOIN_ROOM: 'join_room',
53
LEAVE_ROOM: 'leave_room',
4+
BAN_USER: 'ban_user',
65
};
76

87
const CHATTING_SOCKET_RECEIVE_EVENT = {
@@ -20,28 +19,4 @@ const CHATTING_SOCKET_SEND_EVENT = {
2019
NOTICE: 'send_notice'
2120
};
2221

23-
const CHATTING_SOCKET_ERROR = {
24-
ROOM_EMPTY : {
25-
statusCode: HttpStatus.BAD_REQUEST,
26-
message: '유저가 참여하고 있는 채팅방이 없습니다.'
27-
},
28-
29-
ROOM_EXISTED: {
30-
statusCode: HttpStatus.BAD_REQUEST,
31-
message: '이미 존재하는 방입니다.'
32-
},
33-
34-
INVALID_USER: {
35-
statusCode: HttpStatus.UNAUTHORIZED,
36-
message: '유효하지 않는 유저입니다.'
37-
},
38-
39-
QUESTION_EMPTY: {
40-
statusCode: HttpStatus.BAD_REQUEST,
41-
message: '유효하지 않은 질문입니다.'
42-
},
43-
44-
45-
};
46-
47-
export { CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_SEND_EVENT, CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_ERROR};
22+
export { CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_SEND_EVENT, CHATTING_SOCKET_RECEIVE_EVENT};

backend/chatServer/src/event/dto/OutgoingMessage.dto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class DefaultOutgoingMessageDto {
55
roomId: string = '';
66
nickname: string = '';
77
color: string = '';
8+
entryTime: string = '';
89
msgTime: string = new Date().toISOString();
910
}
1011

backend/chatServer/src/event/dto/Question.dto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class QuestionDto {
44
roomId: string = '';
55
nickname: string = '';
66
color: string = '';
7+
entryTime: string = '';
78
msg: string = '';
89
msgTime: string = new Date().toISOString();
910
msgType: OutgoingMessageType = 'question';

backend/chatServer/src/room/room.repository.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { Injectable } from '@nestjs/common';
22
import { Cluster } from 'ioredis';
3-
import { CHATTING_SOCKET_ERROR } from '../event/constants';
4-
import { WsException } from '@nestjs/websockets';
53
import { QuestionDto } from '../event/dto/Question.dto';
4+
import { ChatException, CHATTING_SOCKET_ERROR } from '../chat/chat.error';
5+
6+
type FORWARDED = string;
67

78
@Injectable()
89
export class RoomRepository {
910
redisClient!: Cluster;
1011
roomIdPrefix = 'room:';
1112
questionPrefix = 'question';
1213
questionIdPrefix = 'id';
14+
blacklistPrefix = 'blacklist';
1315

1416
injectClient(redisClient: Cluster){
1517
this.redisClient = redisClient;
@@ -27,6 +29,14 @@ export class RoomRepository {
2729
return `${this.getRoomStringWithPrefix(roomId)}-${this.questionPrefix}-${this.questionIdPrefix}`;
2830
}
2931

32+
private getUserBlacklistStringWithPrefix(address:string){
33+
return `${this.blacklistPrefix}:${address}`;
34+
}
35+
36+
private getUserBlacklistInRoomWithPrefix(roomId:string, address:string){
37+
return `${this.getRoomStringWithPrefix(roomId)}-${this.getUserBlacklistStringWithPrefix(address)}`;
38+
}
39+
3040
private async lindex<T>(key: string, index: number){
3141
const result = await this.redisClient.lindex(key, index);
3242
if(result) return JSON.parse(result) as T;
@@ -35,12 +45,12 @@ export class RoomRepository {
3545

3646
private async lrange<T>(key: string, start: number, end: number){
3747
const result = await this.redisClient.lrange(key, start, end);
38-
return result.map((r) => JSON.parse(r)) as T;
48+
return result as T;
3949
}
4050

4151
private async getData<T>(key: string) {
4252
const result = await this.redisClient.get(key);
43-
if(result) return JSON.parse(result) as T;
53+
if(result) return typeof result === 'string' ? result as T : JSON.parse(result) as T;
4454
return undefined;
4555
}
4656

@@ -50,7 +60,7 @@ export class RoomRepository {
5060

5161
async getHost(roomId: string) {
5262
const hostId = await this.redisClient.get(this.getRoomStringWithPrefix(roomId));
53-
if(!hostId) throw new WsException(CHATTING_SOCKET_ERROR.ROOM_EMPTY);
63+
if(!hostId) throw new ChatException(CHATTING_SOCKET_ERROR.ROOM_EMPTY);
5464
return hostId;
5565
}
5666

@@ -72,7 +82,7 @@ export class RoomRepository {
7282

7383
async markQuestionAsDone(roomId: string, questionId: number): Promise<QuestionDto> {
7484
const question = await this.getQuestion(roomId, questionId);
75-
if(!question) throw new WsException(CHATTING_SOCKET_ERROR.QUESTION_EMPTY);
85+
if(!question) throw new ChatException(CHATTING_SOCKET_ERROR.QUESTION_EMPTY);
7686
question.questionDone = true;
7787
this.redisClient.lset(this.getQuestionStringWithPrefix(roomId), questionId, JSON.stringify(question));
7888
return question;
@@ -88,7 +98,7 @@ export class RoomRepository {
8898
async getQuestion(roomId: string, questionId: number): Promise<QuestionDto> {
8999
const question = await this.lindex<Omit<QuestionDto, 'questionId'>>(this.getQuestionStringWithPrefix(roomId), questionId);
90100
if(question) return {...question, questionId };
91-
throw new WsException(CHATTING_SOCKET_ERROR.QUESTION_EMPTY);
101+
throw new ChatException(CHATTING_SOCKET_ERROR.QUESTION_EMPTY);
92102
}
93103

94104
async getQuestionId(roomId: string) {
@@ -102,4 +112,16 @@ export class RoomRepository {
102112
}
103113

104114

115+
async getUserBlacklist(roomId: string, address: string): Promise<FORWARDED[]> {
116+
const userBlacklist = await this.lrange<FORWARDED[]>(this.getUserBlacklistInRoomWithPrefix(roomId, address), 0, -1);
117+
console.log('blacklist', userBlacklist);
118+
if (!userBlacklist) return [];
119+
return userBlacklist;
120+
}
121+
122+
async addUserBlacklistToRoom(roomId: string, address: string, forwarded: string){
123+
console.log(roomId, address, forwarded);
124+
console.log(this.getUserBlacklistInRoomWithPrefix(roomId, address));
125+
return this.redisClient.rpush(this.getUserBlacklistInRoomWithPrefix(roomId, address), forwarded);
126+
}
105127
}

0 commit comments

Comments
 (0)