mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-04 10:09:47 +00:00
Validate incoming API data with zod (#891)
## Description: Validate incoming API data with zod. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors --------- Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com>
This commit is contained in:
committed by
1brucben
co-authored by
Scott Anderson
parent
8dc6941fe7
commit
3d032192e1
+1
-1
@@ -112,7 +112,7 @@ export enum LogSeverity {
|
|||||||
Fatal = "FATAL",
|
Fatal = "FATAL",
|
||||||
}
|
}
|
||||||
|
|
||||||
const GameConfigSchema = z.object({
|
export const GameConfigSchema = z.object({
|
||||||
gameMap: z.nativeEnum(GameMapType),
|
gameMap: z.nativeEnum(GameMapType),
|
||||||
difficulty: z.nativeEnum(Difficulty),
|
difficulty: z.nativeEnum(Difficulty),
|
||||||
gameType: z.nativeEnum(GameType),
|
gameType: z.nativeEnum(GameType),
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { GameConfigSchema } from "./Schemas";
|
||||||
|
|
||||||
|
export const CreateGameInputSchema = GameConfigSchema.or(
|
||||||
|
z
|
||||||
|
.object({})
|
||||||
|
.strict()
|
||||||
|
.transform((val) => undefined),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const GameInputSchema = GameConfigSchema.partial();
|
||||||
@@ -58,11 +58,10 @@ export class MapPlaylist {
|
|||||||
infiniteTroops: false,
|
infiniteTroops: false,
|
||||||
instantBuild: false,
|
instantBuild: false,
|
||||||
disableNPCs: mode === GameMode.Team,
|
disableNPCs: mode === GameMode.Team,
|
||||||
disableNukes: false,
|
|
||||||
gameMode: mode,
|
gameMode: mode,
|
||||||
playerTeams: numPlayerTeams,
|
playerTeams: numPlayerTeams,
|
||||||
bots: 400,
|
bots: 400,
|
||||||
} as GameConfig;
|
} satisfies GameConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNextMap(): MapWithMode {
|
private getNextMap(): MapWithMode {
|
||||||
|
|||||||
@@ -282,9 +282,7 @@ async function schedulePublicGame(playlist: MapPlaylist) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
[config.adminHeader()]: config.adminToken(),
|
[config.adminHeader()]: config.adminToken(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(playlist.gameConfig()),
|
||||||
gameConfig: playlist.gameConfig(),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+18
-20
@@ -11,10 +11,10 @@ import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
|||||||
import { GameType } from "../core/game/Game";
|
import { GameType } from "../core/game/Game";
|
||||||
import {
|
import {
|
||||||
ClientMessageSchema,
|
ClientMessageSchema,
|
||||||
GameConfig,
|
|
||||||
GameRecord,
|
GameRecord,
|
||||||
GameRecordSchema,
|
GameRecordSchema,
|
||||||
} from "../core/Schemas";
|
} from "../core/Schemas";
|
||||||
|
import { CreateGameInputSchema, GameInputSchema } from "../core/WorkerSchemas";
|
||||||
import { archive, readGameRecord } from "./Archive";
|
import { archive, readGameRecord } from "./Archive";
|
||||||
import { Client } from "./Client";
|
import { Client } from "./Client";
|
||||||
import { GameManager } from "./GameManager";
|
import { GameManager } from "./GameManager";
|
||||||
@@ -89,7 +89,13 @@ export function startWorker() {
|
|||||||
return res.status(400).json({ error: "Game ID is required" });
|
return res.status(400).json({ error: "Game ID is required" });
|
||||||
}
|
}
|
||||||
const clientIP = req.ip || req.socket.remoteAddress || "unknown";
|
const clientIP = req.ip || req.socket.remoteAddress || "unknown";
|
||||||
const gc = req.body?.gameConfig as GameConfig;
|
const result = CreateGameInputSchema.safeParse(req.body);
|
||||||
|
if (!result.success) {
|
||||||
|
const error = z.prettifyError(result.error);
|
||||||
|
return res.status(400).json({ error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const gc = result.data;
|
||||||
if (
|
if (
|
||||||
gc?.gameType === GameType.Public &&
|
gc?.gameType === GameType.Public &&
|
||||||
req.headers[config.adminHeader()] !== config.adminToken()
|
req.headers[config.adminHeader()] !== config.adminToken()
|
||||||
@@ -97,9 +103,7 @@ export function startWorker() {
|
|||||||
log.warn(
|
log.warn(
|
||||||
`cannot create public game ${id}, ip ${ipAnonymize(clientIP)} incorrect admin token`,
|
`cannot create public game ${id}, ip ${ipAnonymize(clientIP)} incorrect admin token`,
|
||||||
);
|
);
|
||||||
return res
|
return res.status(401).send("Unauthorized");
|
||||||
.status(400)
|
|
||||||
.json({ error: "Invalid admin token for public game creation" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-check this worker should host this game
|
// Double-check this worker should host this game
|
||||||
@@ -144,9 +148,15 @@ export function startWorker() {
|
|||||||
app.put(
|
app.put(
|
||||||
"/api/game/:id",
|
"/api/game/:id",
|
||||||
gatekeeper.httpHandler(LimiterType.Put, async (req, res) => {
|
gatekeeper.httpHandler(LimiterType.Put, async (req, res) => {
|
||||||
|
const result = GameInputSchema.safeParse(req.body);
|
||||||
|
if (!result.success) {
|
||||||
|
const error = z.prettifyError(result.error);
|
||||||
|
return res.status(400).json({ error });
|
||||||
|
}
|
||||||
|
const config = result.data;
|
||||||
// TODO: only update public game if from local host
|
// TODO: only update public game if from local host
|
||||||
const lobbyID = req.params.id;
|
const lobbyID = req.params.id;
|
||||||
if (req.body.gameType === GameType.Public) {
|
if (config.gameType === GameType.Public) {
|
||||||
log.info(`cannot update game ${lobbyID} to public`);
|
log.info(`cannot update game ${lobbyID} to public`);
|
||||||
return res.status(400).json({ error: "Cannot update public game" });
|
return res.status(400).json({ error: "Cannot update public game" });
|
||||||
}
|
}
|
||||||
@@ -167,18 +177,7 @@ export function startWorker() {
|
|||||||
.status(400)
|
.status(400)
|
||||||
.json({ error: "Cannot update game after it has started" });
|
.json({ error: "Cannot update game after it has started" });
|
||||||
}
|
}
|
||||||
game.updateGameConfig({
|
game.updateGameConfig(config);
|
||||||
gameMap: req.body.gameMap,
|
|
||||||
difficulty: req.body.difficulty,
|
|
||||||
infiniteGold: req.body.infiniteGold,
|
|
||||||
infiniteTroops: req.body.infiniteTroops,
|
|
||||||
instantBuild: req.body.instantBuild,
|
|
||||||
bots: req.body.bots,
|
|
||||||
disableNPCs: req.body.disableNPCs,
|
|
||||||
disabledUnits: req.body.disabledUnits,
|
|
||||||
gameMode: req.body.gameMode,
|
|
||||||
playerTeams: req.body.playerTeams,
|
|
||||||
});
|
|
||||||
res.status(200).json({ success: true });
|
res.status(200).json({ success: true });
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -251,8 +250,7 @@ export function startWorker() {
|
|||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
const error = z.prettifyError(result.error);
|
const error = z.prettifyError(result.error);
|
||||||
log.info(error);
|
log.info(error);
|
||||||
res.status(400).json({ error });
|
return res.status(400).json({ error });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameRecord: GameRecord = result.data;
|
const gameRecord: GameRecord = result.data;
|
||||||
|
|||||||
Reference in New Issue
Block a user