From 6a22d58281790b085e0ff4ad3ce5d38e883cdc2a Mon Sep 17 00:00:00 2001 From: Evan Date: Tue, 4 Mar 2025 08:43:17 -0800 Subject: [PATCH] use admin key token for admin auth --- src/core/configuration/Config.ts | 2 ++ src/core/configuration/DefaultConfig.ts | 8 ++++++- src/core/configuration/DevConfig.ts | 5 +++++ src/server/Master.ts | 10 ++------- src/server/Worker.ts | 29 ++++--------------------- 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 7e508e596..466882d88 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -72,6 +72,8 @@ export interface ServerConfig { workerPort(gameID: GameID): number; workerPortByIndex(workerID: number): number; env(): GameEnv; + adminToken(): string; + adminHeader(): string; } export interface Config { diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 7e551ef47..5780acd10 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -22,6 +22,12 @@ import { pastelTheme } from "./PastelTheme"; import { pastelThemeDark } from "./PastelThemeDark"; export abstract class DefaultServerConfig implements ServerConfig { + adminHeader(): string { + return "x-admin-key"; + } + adminToken(): string { + return process.env.ADMIN_TOKEN; + } numWorkers(): number { return 2; } @@ -183,7 +189,7 @@ export class DefaultConfig implements Config { return { cost: (p: Player) => p.type() == PlayerType.Human && this.infiniteGold() - ? 0 + ? 25 : 25_000_000, territoryBound: false, }; diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 2bc1a0c15..fbe3d9bec 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -5,9 +5,14 @@ import { GameEnv, ServerConfig } from "./Config"; import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig"; export class DevServerConfig extends DefaultServerConfig { + adminToken(): string { + return "WARNING_DEV_ADMIN_KEY_DO_NOT_USE_IN_PRODUCTION"; + } + env(): GameEnv { return GameEnv.Dev; } + gameCreationRate(highTraffic: boolean): number { return 5 * 1000; } diff --git a/src/server/Master.ts b/src/server/Master.ts index 802c9f869..7ca635693 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -180,7 +180,6 @@ async function fetchLobbies(): Promise { async function schedulePublicGame() { const gameID = generateID(); publicLobbyIDs.add(gameID); - // Create the default public game config (from your GameManager) const defaultGameConfig = { gameMap: getNextMap(), @@ -192,9 +191,7 @@ async function schedulePublicGame() { disableNPCs: false, bots: 400, }; - const workerPath = config.workerPath(gameID); - // Send request to the worker to start the game try { const response = await fetch( @@ -203,7 +200,8 @@ async function schedulePublicGame() { method: "POST", headers: { "Content-Type": "application/json", - "X-Internal-Request": "true", // Special header for internal requests + "X-Internal-Request": "true", + [config.adminHeader()]: config.adminToken(), }, body: JSON.stringify({ gameID: gameID, @@ -211,11 +209,9 @@ async function schedulePublicGame() { }), }, ); - if (!response.ok) { throw new Error(`Failed to schedule public game: ${response.statusText}`); } - const data = await response.json(); } catch (error) { console.error( @@ -226,11 +222,9 @@ async function schedulePublicGame() { } } -// Map rotation management (moved from GameManager) let mapsPlaylist: GameMapType[] = []; const random = new PseudoRandom(123); -// Get the next map in rotation function getNextMap(): GameMapType { if (mapsPlaylist.length > 0) { return mapsPlaylist.shift()!; diff --git a/src/server/Worker.ts b/src/server/Worker.ts index a85337094..3c6763a71 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -79,9 +79,9 @@ export function startWorker() { // TODO: if game is public make sure request came from localhohst!!! const clientIP = req.ip || req.socket.remoteAddress || "unknown"; const gc = req.body?.gameConfig as GameConfig; - if (gc?.gameType == GameType.Public && !isLocalhost(req)) { + if (gc?.gameType == GameType.Public && !isAdmin(req)) { console.warn( - `cannot create public game ${id}, ip ${clientIP} not localhost`, + `cannot create public game ${id}, ip ${clientIP} not admin`, ); return res.status(400); } @@ -320,27 +320,6 @@ export function startWorker() { }); } -const isLocalhost = (req: Request): boolean => { - // Get client IP address from various possible sources - const clientIP = - req.ip || - req.socket.remoteAddress || - (req.headers["x-forwarded-for"] as string)?.split(",").shift() || - "unknown"; - - // Check if the request is from a loopback address - const isLoopbackIP = - // IPv4 localhost - clientIP === "127.0.0.1" || - // IPv6 localhost - clientIP === "::1" || - // Full loopback range - clientIP.startsWith("127."); - - // Check hostname - const isLocalHostname = - req.hostname === "localhost" || req.headers.host?.startsWith("localhost:"); - - // Consider request local if either IP is loopback or hostname is localhost - return isLoopbackIP || isLocalHostname; +const isAdmin = (req: Request): boolean => { + return req.headers[config.adminHeader()] === config.adminToken(); };