Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/offline-matches/offline-matches.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import {
Controller,
Get,
Post,
Body,
Render,
Req,
Param,
Res,
UseGuards,
UsePipes,
ValidationPipe,
} from "@nestjs/common";
import { OfflineMatchesService } from "./offline-matches.service";
import { MatchData } from "./types/MatchData";
import { type Request, type Response } from "express";
import { type Response } from "express";
import { BasicGuardGuard } from "./basic-guard.guard";
import { KubernetesService } from "src/kubernetes/kubernetes.service";
import { NetworkService } from "src/system/network.service";
Expand All @@ -36,10 +38,12 @@ export class OfflineMatchesController {

@Post("matches")
@UseGuards(BasicGuardGuard)
public async generateYaml(@Req() req: Request, @Res() res: Response) {
await this.offlineMatchesService.generateYamlFiles(
(await req.body) as unknown as MatchData,
);
@UsePipes(new ValidationPipe({ whitelist: true }))
public async generateYaml(
@Body() matchData: MatchData,
@Res() res: Response,
) {
await this.offlineMatchesService.generateYamlFiles(matchData);
return res.redirect("/");
}

Expand All @@ -66,14 +70,13 @@ export class OfflineMatchesController {

@Post("matches/:id")
@UseGuards(BasicGuardGuard)
@UsePipes(new ValidationPipe({ whitelist: true }))
public async updateMatch(
@Param("id") id: string,
@Req() req: Request,
@Body() matchData: MatchData,
@Res() res: Response,
) {
await this.offlineMatchesService.updateMatchData(
(await req.body) as unknown as MatchData,
);
await this.offlineMatchesService.updateMatchData(matchData);
return res.redirect("/");
}

Expand Down
8 changes: 6 additions & 2 deletions src/offline-matches/offline-matches.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,19 @@ export class OfflineMatchesService {
}
}

// Helper function to replace placeholders in YAML template
private sanitizeYamlValue(value: string): string {
return value.replace(/[\r\n]/g, "");
}

private replacePlaceholders(
template: string,
replacements: Record<string, string>,
): string {
let result = template;
for (const [key, value] of Object.entries(replacements)) {
const placeholder = `{{${key}}}`;
result = result.replace(new RegExp(placeholder, "g"), value);
const sanitized = this.sanitizeYamlValue(value);
result = result.split(placeholder).join(sanitized);
}
return result;
}
Expand Down
31 changes: 30 additions & 1 deletion src/offline-matches/types/MatchData.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
import {
IsString,
IsBoolean,
IsArray,
IsOptional,
IsNumber,
} from "class-validator";
import { Lineup } from "./Lineup";
import { MatchMap } from "./MatchMap";
import { MatchOptions } from "./MatchOptions";

export interface MatchData {
export class MatchData {
@IsString()
id: string;

@IsString()
password: string;

@IsString()
lineup_1_id: string;

@IsString()
lineup_2_id: string;

@IsString()
current_match_map_id: string;

options: MatchOptions;

@IsArray()
match_maps: MatchMap[];

lineup_1: Lineup;

lineup_2: Lineup;

@IsBoolean()
is_lan: boolean;

@IsOptional()
@IsNumber()
server_port?: number;

@IsOptional()
@IsNumber()
tv_port?: number;
}
36 changes: 36 additions & 0 deletions src/rcon/rcon.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@ import {
SubscribeMessage,
WebSocketGateway,
} from "@nestjs/websockets";
import { Logger } from "@nestjs/common";
import { RconService } from "../rcon/rcon.service";

@WebSocketGateway()
export class RconGateway {
private readonly logger = new Logger(RconGateway.name);

constructor(private readonly rconService: RconService) {}

private static readonly MAX_COMMAND_LENGTH = 512;

private validateCommand(command: string): string | null {
if (!command || typeof command !== "string") {
return "invalid command";
}
if (command.length > RconGateway.MAX_COMMAND_LENGTH) {
return "command too long";
}
if (/[;\n\r]/.test(command)) {
return "command contains invalid characters";
}
return null;
}

@SubscribeMessage("rcon")
async rconEvent(
@MessageBody()
Expand All @@ -20,6 +38,24 @@ export class RconGateway {
},
@ConnectedSocket() client: WebSocket,
) {
const validationError = this.validateCommand(data.command);
if (validationError) {
client.send(
JSON.stringify({
event: "rcon",
data: {
uuid: data.uuid,
result: validationError,
},
}),
);
return;
}

this.logger.log(
`RCON [${data.matchId}]: ${data.command}`,
);

const rcon = await this.rconService.connect(data.matchId);

if (!rcon) {
Expand Down
Loading