diff --git a/src/main.ts b/src/main.ts index 5ad4b5f7..ad4c8751 100644 --- a/src/main.ts +++ b/src/main.ts @@ -63,10 +63,7 @@ async function bootstrap() { }, }); - app.set("trust proxy", () => { - // TODO - trust proxy - return true; - }); + app.set("trust proxy", 1); const appConfig = configService.get("app"); diff --git a/src/matches/match-events.gateway.ts b/src/matches/match-events.gateway.ts index 634ed9f2..4686d481 100644 --- a/src/matches/match-events.gateway.ts +++ b/src/matches/match-events.gateway.ts @@ -4,6 +4,7 @@ import { SubscribeMessage, WebSocketGateway, } from "@nestjs/websockets"; +import { timingSafeEqual } from "crypto"; import WebSocket from "ws"; import { Request } from "express"; import { ModuleRef } from "@nestjs/core"; @@ -29,6 +30,11 @@ export class MatchEventsGateway { private readonly cache: CacheService, ) {} + private safeCompare(a: string, b: string): boolean { + if (a.length !== b.length) return false; + return timingSafeEqual(Buffer.from(a), Buffer.from(b)); + } + async handleConnection( @ConnectedSocket() client: WebSocket.WebSocket, request: Request, @@ -36,30 +42,46 @@ export class MatchEventsGateway { try { const authHeader = request.headers.authorization; - if (authHeader && authHeader.startsWith("Basic ")) { - const base64Credentials = authHeader.split(" ").at(1); + if (!authHeader || !authHeader.startsWith("Basic ")) { + client.close(); + return; + } + + const base64Credentials = authHeader.split(" ").at(1); + if (!base64Credentials) { + client.close(); + return; + } + + const decoded = Buffer.from(base64Credentials, "base64").toString(); + const colonIndex = decoded.indexOf(":"); + if (colonIndex === -1) { + client.close(); + return; + } - const [serverId, apiPassword] = Buffer.from(base64Credentials, "base64") - .toString() - .split(":"); + const serverId = decoded.substring(0, colonIndex); + const apiPassword = decoded.substring(colonIndex + 1); - const { servers_by_pk } = await this.hasura.query({ - servers_by_pk: { - __args: { - id: serverId, - }, - id: true, - api_password: true, + const { servers_by_pk } = await this.hasura.query({ + servers_by_pk: { + __args: { + id: serverId, }, + id: true, + api_password: true, + }, + }); + + if ( + !servers_by_pk?.api_password || + !this.safeCompare(servers_by_pk.api_password, apiPassword) + ) { + client.close(); + this.logger.warn("game server auth failure", { + serverId, + ip: request.headers["cf-connecting-ip"], }); - - if (servers_by_pk?.api_password !== apiPassword) { - client.close(); - this.logger.warn("game server auth failure", { - serverId, - ip: request.headers["cf-connecting-ip"], - }); - } } } catch { client.close(); diff --git a/src/matches/match-relay/match-relay-auth-middleware.ts b/src/matches/match-relay/match-relay-auth-middleware.ts index 01e5d234..b66e513c 100644 --- a/src/matches/match-relay/match-relay-auth-middleware.ts +++ b/src/matches/match-relay/match-relay-auth-middleware.ts @@ -1,3 +1,4 @@ +import { timingSafeEqual } from "crypto"; import { CacheService } from "src/cache/cache.service"; import { Request, Response, NextFunction } from "express"; import { HasuraService } from "src/hasura/hasura.service"; @@ -11,11 +12,25 @@ export class MatchRelayAuthMiddleware implements NestMiddleware { private readonly hasura: HasuraService, ) {} + private safeCompare(a: string, b: string): boolean { + if (a.length !== b.length) return false; + return timingSafeEqual(Buffer.from(a), Buffer.from(b)); + } + async use(request: Request, response: Response, next: NextFunction) { try { - const [matchId, apiPassword] = ( - request.headers["x-origin-auth"] as string - )?.split(":"); + const originAuth = request.headers["x-origin-auth"]; + if (!originAuth || typeof originAuth !== "string") { + return response.status(401).end(); + } + + const colonIndex = originAuth.indexOf(":"); + if (colonIndex === -1) { + return response.status(401).end(); + } + + const matchId = originAuth.substring(0, colonIndex); + const apiPassword = originAuth.substring(colonIndex + 1); const token = request.url.split("/")?.[3]; @@ -36,7 +51,7 @@ export class MatchRelayAuthMiddleware implements NestMiddleware { 60 * 1000, ); - if (matchPassword !== apiPassword) { + if (!matchPassword || !this.safeCompare(matchPassword, apiPassword)) { return response.status(401).end(); } } catch (error) {