diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index 2535eb5..d457550 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -9,6 +9,8 @@ import { movePlayerUseCase } from "@server/domains/game/application/useCases/movePlayerUseCase"; import { createCommonHandlerContext } from "@server/network/handlers/CommonHandler"; import { createGameEventPublisher } from "./createGameEventPublisher"; +import { logEvent } from "@server/logging/logEvent"; +import { isMovePayload, isPingPayload } from "@server/network/validation/socketPayloadValidators"; export const registerGameHandlers = ( io: Server, @@ -19,7 +21,16 @@ const common = createCommonHandlerContext(io, socket); const gamePublisher = createGameEventPublisher(common); - socket.on(protocol.SocketEvents.PING, (clientTime: number) => { + socket.on(protocol.SocketEvents.PING, (clientTime: unknown) => { + if (!isPingPayload(clientTime)) { + logEvent("Network", { + event: "PING", + result: "ignored_invalid_payload", + socketId: socket.id, + }); + return; + } + pingUseCase({ clientTime, publishPong: gamePublisher.publishPongToSocket, @@ -50,7 +61,16 @@ }); }); - socket.on(protocol.SocketEvents.MOVE, (data: playerTypes.MovePayload) => { + socket.on(protocol.SocketEvents.MOVE, (data: unknown) => { + if (!isMovePayload(data)) { + logEvent("Network", { + event: "MOVE", + result: "ignored_invalid_payload", + socketId: socket.id, + }); + return; + } + movePlayerUseCase({ gameManager, playerId: socket.id, diff --git a/apps/server/src/network/handlers/room/registerRoomHandlers.ts b/apps/server/src/network/handlers/room/registerRoomHandlers.ts index be531ca..2d92107 100644 --- a/apps/server/src/network/handlers/room/registerRoomHandlers.ts +++ b/apps/server/src/network/handlers/room/registerRoomHandlers.ts @@ -5,6 +5,8 @@ import { joinRoomUseCase } from "@server/domains/room/application/useCases/joinRoomUseCase"; import { createCommonHandlerContext } from "@server/network/handlers/CommonHandler"; import { createRoomEventPublisher } from "./createRoomEventPublisher"; +import { isJoinRoomPayload } from "@server/network/validation/socketPayloadValidators"; +import { logEvent } from "@server/logging/logEvent"; export const registerRoomHandlers = ( io: Server, @@ -14,7 +16,16 @@ const common = createCommonHandlerContext(io, socket); const roomPublisher = createRoomEventPublisher(common); - socket.on(protocol.SocketEvents.JOIN_ROOM, (data: roomTypes.JoinRoomPayload) => { + socket.on(protocol.SocketEvents.JOIN_ROOM, (data: unknown) => { + if (!isJoinRoomPayload(data)) { + logEvent("Network", { + event: "JOIN_ROOM", + result: "ignored_invalid_payload", + socketId: socket.id, + }); + return; + } + const { roomId } = data; socket.join(roomId); diff --git a/apps/server/src/network/validation/socketPayloadValidators.ts b/apps/server/src/network/validation/socketPayloadValidators.ts new file mode 100644 index 0000000..fffc822 --- /dev/null +++ b/apps/server/src/network/validation/socketPayloadValidators.ts @@ -0,0 +1,31 @@ +import type { playerTypes, roomTypes } from "@repo/shared"; + +const isFiniteNumber = (value: unknown): value is number => { + return typeof value === "number" && Number.isFinite(value); +}; + +const isNonEmptyString = (value: unknown): value is string => { + return typeof value === "string" && value.trim().length > 0; +}; + +export const isPingPayload = (value: unknown): value is number => { + return isFiniteNumber(value); +}; + +export const isMovePayload = (value: unknown): value is playerTypes.MovePayload => { + if (typeof value !== "object" || value === null) { + return false; + } + + const candidate = value as Record; + return isFiniteNumber(candidate.x) && isFiniteNumber(candidate.y); +}; + +export const isJoinRoomPayload = (value: unknown): value is roomTypes.JoinRoomPayload => { + if (typeof value !== "object" || value === null) { + return false; + } + + const candidate = value as Record; + return isNonEmptyString(candidate.roomId) && isNonEmptyString(candidate.playerName); +};