From 5a664a14a9c15b8ead680a09b04b06f5bebfb842 Mon Sep 17 00:00:00 2001 From: Flegma Date: Wed, 8 Apr 2026 12:56:54 +0200 Subject: [PATCH 1/3] fix: add lobby verification lock and sockets gateway disconnect handler Wrap matchmaking verify+setLobbyDetails+addLobbyToQueue in a Redis lock to prevent a banned player from bypassing verification between the check and queue addition. Add handleDisconnect lifecycle hook to SocketsGateway and make handleConnection public for proper NestJS gateway lifecycle support. --- src/matchmaking/matchmaking.gateway.ts | 27 ++++++++++++++++++-------- src/matchmaking/matchmaking.module.ts | 2 ++ src/sockets/sockets.gateway.ts | 6 +++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/matchmaking/matchmaking.gateway.ts b/src/matchmaking/matchmaking.gateway.ts index 48b294c0..ced4c31d 100644 --- a/src/matchmaking/matchmaking.gateway.ts +++ b/src/matchmaking/matchmaking.gateway.ts @@ -4,6 +4,7 @@ import { e_match_types_enum } from "generated"; import { MatchmakeService } from "./matchmake.service"; import { MatchmakingLobbyService } from "./matchmaking-lobby.service"; import { RedisManagerService } from "../redis/redis-manager/redis-manager.service"; +import { CacheService } from "src/cache/cache.service"; import { FiveStackWebSocketClient } from "src/sockets/types/FiveStackWebSocketClient"; import { ConnectedSocket, @@ -12,6 +13,7 @@ import { WebSocketGateway, } from "@nestjs/websockets"; import { JoinQueueError } from "./utilities/joinQueueError"; +import { PlayerLobby } from "./types/PlayerLobby"; import { HasuraService } from "src/hasura/hasura.service"; import { isRoleAbove } from "src/utilities/isRoleAbove"; import { e_player_roles_enum } from "generated"; @@ -29,6 +31,7 @@ export class MatchmakingGateway { public readonly redisManager: RedisManagerService, public readonly matchmakeService: MatchmakeService, public readonly matchmakingLobbyService: MatchmakingLobbyService, + private readonly cache: CacheService, ) { this.redis = this.redisManager.getConnection(); } @@ -122,7 +125,7 @@ export class MatchmakingGateway { throw new JoinQueueError("You do not have permission to join this queue"); } - let lobby; + let lobby: PlayerLobby | undefined; const user = client.user; if (!user) { @@ -220,16 +223,24 @@ export class MatchmakingGateway { throw new JoinQueueError("Unable to find Player Lobby"); } - await this.matchmakingLobbyService.verifyLobby(lobby, user, type); - try { - await this.matchmakingLobbyService.setLobbyDetails( - regions, - type, - lobby, + await this.cache.lock( + `matchmaking:verify:${lobby.id}`, + async () => { + await this.matchmakingLobbyService.verifyLobby(lobby, user, type); + await this.matchmakingLobbyService.setLobbyDetails( + regions, + type, + lobby, + ); + await this.matchmakeService.addLobbyToQueue(lobby.id); + return true; + }, ); - await this.matchmakeService.addLobbyToQueue(lobby.id); } catch (error) { + if (error instanceof JoinQueueError) { + throw error; + } this.logger.error(`unable to add lobby to queue`, error); await this.matchmakingLobbyService.removeLobbyFromQueue(lobby.id); await this.matchmakingLobbyService.removeLobbyDetails(lobby.id); diff --git a/src/matchmaking/matchmaking.module.ts b/src/matchmaking/matchmaking.module.ts index 298aa708..1aa8383a 100644 --- a/src/matchmaking/matchmaking.module.ts +++ b/src/matchmaking/matchmaking.module.ts @@ -3,6 +3,7 @@ import { loggerFactory } from "../utilities/LoggerFactory"; import { MatchmakingGateway } from "./matchmaking.gateway"; import { HasuraModule } from "src/hasura/hasura.module"; import { RedisModule } from "src/redis/redis.module"; +import { CacheModule } from "src/cache/cache.module"; import { MatchesModule } from "src/matches/matches.module"; import { MatchmakeService } from "./matchmake.service"; import { MatchmakingLobbyService } from "./matchmaking-lobby.service"; @@ -19,6 +20,7 @@ import { MarkPlayerOffline } from "./jobs/MarkPlayerOffline"; imports: [ RedisModule, HasuraModule, + CacheModule, forwardRef(() => MatchesModule), BullModule.registerQueue({ name: MatchmakingQueues.Matchmaking, diff --git a/src/sockets/sockets.gateway.ts b/src/sockets/sockets.gateway.ts index 037282e5..75ae562a 100644 --- a/src/sockets/sockets.gateway.ts +++ b/src/sockets/sockets.gateway.ts @@ -22,10 +22,14 @@ export class SocketsGateway { await this.sockets.updateClient(client.user.steam_id, client.id); } - private async handleConnection( + public async handleConnection( @ConnectedSocket() client: FiveStackWebSocketClient, request: Request, ) { await this.sockets.setupSocket(client, request); } + + public handleDisconnect(client: FiveStackWebSocketClient) { + // Cleanup is handled by client.on("close") in SocketsService.setupSocket + } } From cf3bf65dc122e45fc8fa96b8c9eb89df19cea18d Mon Sep 17 00:00:00 2001 From: Flegma Date: Wed, 8 Apr 2026 13:03:57 +0200 Subject: [PATCH 2/3] fix: implement OnGatewayConnection and OnGatewayDisconnect interfaces Adds compile-time type safety for NestJS WebSocket lifecycle hooks. --- src/sockets/sockets.gateway.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sockets/sockets.gateway.ts b/src/sockets/sockets.gateway.ts index 75ae562a..5f922690 100644 --- a/src/sockets/sockets.gateway.ts +++ b/src/sockets/sockets.gateway.ts @@ -1,5 +1,7 @@ import { ConnectedSocket, + OnGatewayConnection, + OnGatewayDisconnect, SubscribeMessage, WebSocketGateway, } from "@nestjs/websockets"; @@ -10,7 +12,9 @@ import { SocketsService } from "./sockets.service"; @WebSocketGateway({ path: "/ws/web", }) -export class SocketsGateway { +export class SocketsGateway + implements OnGatewayConnection, OnGatewayDisconnect +{ constructor(private readonly sockets: SocketsService) {} @SubscribeMessage("ping") From 5faf2611b17342bab370cd51121effe5af2e8e4b Mon Sep 17 00:00:00 2001 From: Flegma Date: Fri, 10 Apr 2026 12:24:20 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20remove=20empty=20handleDisconnect=20?= =?UTF-8?q?=E2=80=94=20cleanup=20already=20handled=20by=20close=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sockets/sockets.gateway.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/sockets/sockets.gateway.ts b/src/sockets/sockets.gateway.ts index 5f922690..d4cf8839 100644 --- a/src/sockets/sockets.gateway.ts +++ b/src/sockets/sockets.gateway.ts @@ -1,7 +1,6 @@ import { ConnectedSocket, OnGatewayConnection, - OnGatewayDisconnect, SubscribeMessage, WebSocketGateway, } from "@nestjs/websockets"; @@ -12,9 +11,7 @@ import { SocketsService } from "./sockets.service"; @WebSocketGateway({ path: "/ws/web", }) -export class SocketsGateway - implements OnGatewayConnection, OnGatewayDisconnect -{ +export class SocketsGateway implements OnGatewayConnection { constructor(private readonly sockets: SocketsService) {} @SubscribeMessage("ping") @@ -32,8 +29,4 @@ export class SocketsGateway ) { await this.sockets.setupSocket(client, request); } - - public handleDisconnect(client: FiveStackWebSocketClient) { - // Cleanup is handled by client.on("close") in SocketsService.setupSocket - } }