Showing
8 changed files
with
133 additions
and
44 deletions
1 | +import { | ||
2 | + Boolean, | ||
3 | + Number, | ||
4 | + String, | ||
5 | + Literal, | ||
6 | + Array, | ||
7 | + Tuple, | ||
8 | + Record, | ||
9 | + Union, | ||
10 | + Static, | ||
11 | +} from "runtypes"; | ||
12 | + | ||
13 | +export const UserDataRecord = Record({ | ||
14 | + username: String, | ||
15 | +}); | ||
16 | + | ||
17 | +export type UserData = Static<typeof UserDataRecord>; | ||
18 | + | ||
1 | /** | 19 | /** |
2 | * 방 리스트에서 사용됩니다. | 20 | * 방 리스트에서 사용됩니다. |
3 | */ | 21 | */ |
4 | -export interface RoomDescription { | 22 | +export const RoomDescriptionRecord = Record({ |
5 | - uuid: string; | 23 | + uuid: String, |
6 | - name: string; | 24 | + name: String, |
7 | - currentUsers: number; | 25 | + currentUsers: Number, |
8 | - maxUsers: number; | 26 | + maxUsers: Number, |
9 | -} | 27 | +}); |
28 | + | ||
29 | +export type RoomDescription = Static<typeof RoomDescriptionRecord>; | ||
10 | 30 | ||
11 | /** | 31 | /** |
12 | * 방에 접속했을 때 사용됩니다. | 32 | * 방에 접속했을 때 사용됩니다. |
13 | */ | 33 | */ |
14 | -export interface RoomInfo { | 34 | +export const RoomInfoRecord = Record({ |
15 | - uuid: string; | 35 | + uuid: String, |
16 | - name: string; | 36 | + name: String, |
17 | - maxUsers: number; | 37 | + maxUsers: Number, |
18 | - users: UserData[]; | 38 | + users: Array(UserDataRecord), |
19 | -} | 39 | +}); |
20 | 40 | ||
21 | -export interface UserData { | 41 | +export type RoomInfo = Static<typeof RoomInfoRecord>; |
22 | - username: string; | ||
23 | -} | ||
24 | 42 | ||
25 | export type Role = "drawer" | "guesser" | "winner" | "spectator"; | 43 | export type Role = "drawer" | "guesser" | "winner" | "spectator"; | ... | ... |
1 | -import { Role, RoomDescription, RoomInfo } from "./dataType"; | 1 | +import { |
2 | + Boolean, | ||
3 | + Number, | ||
4 | + String, | ||
5 | + Literal, | ||
6 | + Array, | ||
7 | + Tuple, | ||
8 | + Record, | ||
9 | + Union, | ||
10 | + Static, | ||
11 | +} from "runtypes"; | ||
12 | +import { | ||
13 | + Role, | ||
14 | + RoomDescription, | ||
15 | + RoomDescriptionRecord, | ||
16 | + RoomInfo, | ||
17 | + RoomInfoRecord, | ||
18 | +} from "./dataType"; | ||
2 | 19 | ||
3 | // 서버로 들어오는 메세지 타입을 정의합니다. | 20 | // 서버로 들어오는 메세지 타입을 정의합니다. |
4 | // 'result' 속성은 서버 요청 결과에만 포함되는 특별한 속성입니다. | 21 | // 'result' 속성은 서버 요청 결과에만 포함되는 특별한 속성입니다. |
5 | -interface ServerInboundMessageMap { | 22 | +export class ServerInboundMessageRecordMap { |
6 | // 로그인을 시도합니다. | 23 | // 로그인을 시도합니다. |
7 | - login: { | 24 | + login = Record({ username: String }); |
8 | - username: string; | ||
9 | - }; | ||
10 | 25 | ||
11 | // 방 목록을 요청합니다. | 26 | // 방 목록을 요청합니다. |
12 | - roomList: { | 27 | + roomList = Record({ |
13 | - result: RoomDescription[]; | 28 | + result: Array(RoomDescriptionRecord), |
14 | - }; | 29 | + }); |
15 | 30 | ||
16 | // 방에 접속합니다. | 31 | // 방에 접속합니다. |
17 | - joinRoom: { | 32 | + joinRoom = Record({ |
18 | - uuid: string; | 33 | + uuid: String, |
19 | - result: RoomInfo; | 34 | + result: RoomInfoRecord, |
20 | - }; | 35 | + }); |
21 | 36 | ||
22 | // 방에서 나갑니다. | 37 | // 방에서 나갑니다. |
23 | - leaveRoom: {}; | 38 | + leaveRoom = Record({}); |
24 | 39 | ||
25 | // 채팅을 보냅니다. | 40 | // 채팅을 보냅니다. |
26 | - chat: { | 41 | + chat = Record({ |
27 | - message: string; | 42 | + message: String, |
28 | - }; | 43 | + }); |
29 | 44 | ||
30 | // drawer가 단어를 선택합니다. | 45 | // drawer가 단어를 선택합니다. |
31 | - chooseWord: { | 46 | + chooseWord = Record({ |
32 | - word: string; | 47 | + word: String, |
33 | - }; | 48 | + }); |
34 | 49 | ||
35 | // 브러시 정보를 변경합니다. | 50 | // 브러시 정보를 변경합니다. |
36 | - setBrush: { | 51 | + setBrush = Record({ |
37 | - size: number; | 52 | + size: Number, |
38 | - color: string; | 53 | + color: String, |
39 | - drawing: boolean; | 54 | + drawing: Boolean, |
40 | - }; | 55 | + }); |
41 | 56 | ||
42 | // 브러시를 이동합니다. | 57 | // 브러시를 이동합니다. |
43 | - moveBrush: { | 58 | + moveBrush = Record({ |
44 | - x: number; | 59 | + x: Number, |
45 | - y: number; | 60 | + y: Number, |
46 | - }; | 61 | + }); |
47 | } | 62 | } |
48 | 63 | ||
64 | +type ServerInboundMessageMap = { | ||
65 | + [Key in keyof ServerInboundMessageRecordMap]: Static< | ||
66 | + ServerInboundMessageRecordMap[Key] | ||
67 | + >; | ||
68 | +}; | ||
69 | + | ||
49 | // 서버에서 나가는 메세지 타입을 정의합니다. | 70 | // 서버에서 나가는 메세지 타입을 정의합니다. |
50 | interface ServerOutboundMessageMap { | 71 | interface ServerOutboundMessageMap { |
51 | // 방에 접속 중인 유저 목록이 업데이트 되었습니다. | 72 | // 방에 접속 중인 유저 목록이 업데이트 되었습니다. | ... | ... |
... | @@ -2,3 +2,7 @@ | ... | @@ -2,3 +2,7 @@ |
2 | # yarn lockfile v1 | 2 | # yarn lockfile v1 |
3 | 3 | ||
4 | 4 | ||
5 | +runtypes@^6.3.0: | ||
6 | + version "6.3.0" | ||
7 | + resolved "https://registry.yarnpkg.com/runtypes/-/runtypes-6.3.0.tgz#bd88392c21f471bd45591d5eabaa4644ca7cdf3c" | ||
8 | + integrity sha512-FTNUs13CIrCTjReBOaeY/8EY1LYIQVkkwyE9z5MCjZe9uew9/8TRbWF1PcTczgTFfGBjkjUKeedFWU2O3ExjPg== | ... | ... |
... | @@ -10,12 +10,15 @@ import { | ... | @@ -10,12 +10,15 @@ import { |
10 | import { Room } from "../room/Room"; | 10 | import { Room } from "../room/Room"; |
11 | import { RoomManager } from "../room/RoomManager"; | 11 | import { RoomManager } from "../room/RoomManager"; |
12 | import { User } from "../user/User"; | 12 | import { User } from "../user/User"; |
13 | +import { MessageValidator } from "./MessageValidator"; | ||
13 | import { SocketWrapper } from "./SocketWrapper"; | 14 | import { SocketWrapper } from "./SocketWrapper"; |
14 | 15 | ||
15 | export class Connection { | 16 | export class Connection { |
16 | public readonly socket: SocketWrapper; | 17 | public readonly socket: SocketWrapper; |
17 | public readonly roomManager: RoomManager; | 18 | public readonly roomManager: RoomManager; |
18 | 19 | ||
20 | + static readonly validator: MessageValidator = new MessageValidator(); | ||
21 | + | ||
19 | public user?: User; | 22 | public user?: User; |
20 | 23 | ||
21 | constructor(socket: SocketWrapper, roomManager: RoomManager) { | 24 | constructor(socket: SocketWrapper, roomManager: RoomManager) { |
... | @@ -38,6 +41,10 @@ export class Connection { | ... | @@ -38,6 +41,10 @@ export class Connection { |
38 | const type = raw.type as ServerInboundMessageKey; | 41 | const type = raw.type as ServerInboundMessageKey; |
39 | const message = raw.message; | 42 | const message = raw.message; |
40 | 43 | ||
44 | + if (!Connection.validator.validate(type, message)) { | ||
45 | + return { ok: false }; | ||
46 | + } | ||
47 | + | ||
41 | // 유저 정보가 없으므로 로그인은 따로 핸들링 | 48 | // 유저 정보가 없으므로 로그인은 따로 핸들링 |
42 | if (type === "login") { | 49 | if (type === "login") { |
43 | return this.handleLogin(message); | 50 | return this.handleLogin(message); | ... | ... |
server/connection/MessageValidator.ts
0 → 100644
1 | +import { | ||
2 | + ServerInboundMessageKey, | ||
3 | + ServerInboundMessageRecordMap, | ||
4 | +} from "../../common"; | ||
5 | +import { Record } from "runtypes"; | ||
6 | + | ||
7 | +export class MessageValidator { | ||
8 | + private readonly messageRecordMap: Map< | ||
9 | + ServerInboundMessageKey, | ||
10 | + Record<any, boolean> | ||
11 | + > = new Map(); | ||
12 | + constructor() { | ||
13 | + const messageRecordMapContainsResult = new ServerInboundMessageRecordMap(); | ||
14 | + for (const key in messageRecordMapContainsResult) { | ||
15 | + const recordContainsResult = | ||
16 | + messageRecordMapContainsResult[ | ||
17 | + key as keyof ServerInboundMessageRecordMap | ||
18 | + ]; | ||
19 | + //@ts-ignore because some of entries don't have result property. | ||
20 | + this.messageRecordMap.set(key, recordContainsResult.omit("result")); | ||
21 | + } | ||
22 | + } | ||
23 | + | ||
24 | + public validate(key: ServerInboundMessageKey, message: any) { | ||
25 | + const messageRecord = this.messageRecordMap.get(key); | ||
26 | + if (messageRecord) { | ||
27 | + return messageRecord.validate(message).success; | ||
28 | + } | ||
29 | + return false; | ||
30 | + } | ||
31 | +} |
... | @@ -13,6 +13,7 @@ | ... | @@ -13,6 +13,7 @@ |
13 | "mocha": "^8.4.0", | 13 | "mocha": "^8.4.0", |
14 | "mocha-steps": "^1.3.0", | 14 | "mocha-steps": "^1.3.0", |
15 | "nodemon": "^2.0.7", | 15 | "nodemon": "^2.0.7", |
16 | + "runtypes": "^6.3.0", | ||
16 | "socket.io": "^4.1.2", | 17 | "socket.io": "^4.1.2", |
17 | "socket.io-client": "^4.1.2", | 18 | "socket.io-client": "^4.1.2", |
18 | "ts-node": "^9.1.1", | 19 | "ts-node": "^9.1.1", | ... | ... |
... | @@ -1400,6 +1400,11 @@ responselike@^1.0.2: | ... | @@ -1400,6 +1400,11 @@ responselike@^1.0.2: |
1400 | dependencies: | 1400 | dependencies: |
1401 | lowercase-keys "^1.0.0" | 1401 | lowercase-keys "^1.0.0" |
1402 | 1402 | ||
1403 | +runtypes@^6.3.0: | ||
1404 | + version "6.3.0" | ||
1405 | + resolved "https://registry.yarnpkg.com/runtypes/-/runtypes-6.3.0.tgz#bd88392c21f471bd45591d5eabaa4644ca7cdf3c" | ||
1406 | + integrity sha512-FTNUs13CIrCTjReBOaeY/8EY1LYIQVkkwyE9z5MCjZe9uew9/8TRbWF1PcTczgTFfGBjkjUKeedFWU2O3ExjPg== | ||
1407 | + | ||
1403 | safe-buffer@5.1.2: | 1408 | safe-buffer@5.1.2: |
1404 | version "5.1.2" | 1409 | version "5.1.2" |
1405 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" | 1410 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" | ... | ... |
-
Please register or login to post a comment