mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-02 15:08:33 +00:00
Update to Gatekeeper
This commit is contained in:
@@ -19,7 +19,7 @@ import { GameType } from "../core/game/Game";
|
||||
import { archive } from "./Archive";
|
||||
import { Client } from "./Client";
|
||||
import { slog } from "./StructuredLog";
|
||||
import { securityMiddleware } from "./Security";
|
||||
import { gatekeeper } from "./Gatekeeper";
|
||||
|
||||
export enum GamePhase {
|
||||
Lobby = "LOBBY",
|
||||
@@ -123,7 +123,7 @@ export class GameServer {
|
||||
|
||||
client.ws.on(
|
||||
"message",
|
||||
securityMiddleware.wsHandler(client.ip, async (message: string) => {
|
||||
gatekeeper.wsHandler(client.ip, async (message: string) => {
|
||||
try {
|
||||
let clientMsg: ClientMessage = null;
|
||||
try {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// src/server/middleware/securityInterface.ts
|
||||
// src/server/Security.ts
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import http from "http";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import fs from "fs";
|
||||
|
||||
export enum LimiterType {
|
||||
Get = "get",
|
||||
@@ -11,11 +12,11 @@ export enum LimiterType {
|
||||
WebSocket = "websocket",
|
||||
}
|
||||
|
||||
export interface SecurityMiddleware {
|
||||
export interface Gatekeeper {
|
||||
// The wrapper for request handlers with optional rate limiting
|
||||
httpHandler: (
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
||||
limiterType: LimiterType,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
||||
) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
|
||||
// The wrapper for WebSocket message handlers with rate limiting
|
||||
@@ -26,41 +27,63 @@ export interface SecurityMiddleware {
|
||||
}
|
||||
|
||||
// Function to get the appropriate security middleware implementation
|
||||
async function getSecurityMiddleware(): Promise<SecurityMiddleware> {
|
||||
async function getGatekeeper(): Promise<Gatekeeper> {
|
||||
try {
|
||||
// Get the current file's directory
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
try {
|
||||
// Use dynamic import for ES modules - without file extension
|
||||
// ts-node will resolve this correctly
|
||||
const module = await import(
|
||||
"./security-middleware/RealSecurityMiddleware"
|
||||
// Check if the file exists before attempting to import it
|
||||
const realMiddlewarePath = path.resolve(
|
||||
__dirname,
|
||||
"./gatekeeper/RealSecurityMiddleware.js",
|
||||
);
|
||||
const tsMiddlewarePath = path.resolve(
|
||||
__dirname,
|
||||
"./gatekeeper/RealSecurityMiddleware.ts",
|
||||
);
|
||||
|
||||
if (!module.RealSecurityMiddleware) {
|
||||
throw new Error("RealSecurityMiddleware class not found in module");
|
||||
if (
|
||||
!fs.existsSync(realMiddlewarePath) &&
|
||||
!fs.existsSync(tsMiddlewarePath)
|
||||
) {
|
||||
console.log(
|
||||
"RealSecurityMiddleware file not found, using NoOpSecurityMiddleware",
|
||||
);
|
||||
return new NoOpGatekeeper();
|
||||
}
|
||||
|
||||
// Use dynamic import for ES modules
|
||||
const module = await import("./gatekeeper/RealGatekeeper.js").catch(
|
||||
() => import("./gatekeeper/RealGatekeeper.js"),
|
||||
);
|
||||
|
||||
if (!module || !module.RealSecurityMiddleware) {
|
||||
console.log(
|
||||
"RealSecurityMiddleware class not found in module, using NoOpSecurityMiddleware",
|
||||
);
|
||||
return new NoOpGatekeeper();
|
||||
}
|
||||
|
||||
console.log("Successfully loaded real security middleware");
|
||||
return new module.RealSecurityMiddleware();
|
||||
} catch (error) {
|
||||
console.log("Failed to load real security middleware:", error);
|
||||
return new NoOpSecurityMiddleware();
|
||||
return new NoOpGatekeeper();
|
||||
}
|
||||
} catch (e) {
|
||||
// Fall back to no-op if real implementation isn't available
|
||||
console.log("using no-op security middleware", e);
|
||||
return new NoOpSecurityMiddleware();
|
||||
return new NoOpGatekeeper();
|
||||
}
|
||||
}
|
||||
|
||||
export class NoOpSecurityMiddleware implements SecurityMiddleware {
|
||||
export class NoOpGatekeeper implements Gatekeeper {
|
||||
// Simple pass-through with no rate limiting
|
||||
httpHandler(
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
||||
limiterType: LimiterType,
|
||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
|
||||
) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
@@ -89,14 +112,13 @@ export class NoOpSecurityMiddleware implements SecurityMiddleware {
|
||||
// Initialize the security middleware with a default implementation
|
||||
// We'll use the NoOpSecurityMiddleware initially and then replace it
|
||||
// with the real implementation once it's loaded
|
||||
export const securityMiddleware: SecurityMiddleware =
|
||||
new NoOpSecurityMiddleware();
|
||||
export const gatekeeper: Gatekeeper = new NoOpGatekeeper();
|
||||
|
||||
// Immediately try to load the real middleware
|
||||
getSecurityMiddleware()
|
||||
getGatekeeper()
|
||||
.then((middleware) => {
|
||||
// Replace the methods of securityMiddleware with those from the loaded middleware
|
||||
Object.assign(securityMiddleware, middleware);
|
||||
Object.assign(gatekeeper, middleware);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to initialize security middleware:", error);
|
||||
@@ -10,6 +10,7 @@ import path from "path";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import { fileURLToPath } from "url";
|
||||
import { isHighTrafficTime } from "./Util";
|
||||
import { gatekeeper, LimiterType } from "./Gatekeeper";
|
||||
|
||||
const config = getServerConfig();
|
||||
const readyWorkers = new Set();
|
||||
@@ -122,9 +123,12 @@ export async function startMaster() {
|
||||
}
|
||||
|
||||
// Add lobbies endpoint to list public games for this worker
|
||||
app.get("/public_lobbies", (req, res) => {
|
||||
res.send(publicLobbiesJsonStr);
|
||||
});
|
||||
app.get(
|
||||
"/public_lobbies",
|
||||
gatekeeper.httpHandler(LimiterType.Get, async (req, res) => {
|
||||
res.send(publicLobbiesJsonStr);
|
||||
}),
|
||||
);
|
||||
|
||||
async function fetchLobbies(): Promise<void> {
|
||||
const fetchPromises = [];
|
||||
|
||||
+14
-14
@@ -13,7 +13,7 @@ import { GameConfig, GameRecord, LogSeverity } from "../core/Schemas";
|
||||
import { slog } from "./StructuredLog";
|
||||
import { GameType } from "../core/game/Game";
|
||||
import { archive } from "./Archive";
|
||||
import { LimiterType, securityMiddleware } from "./Security";
|
||||
import { LimiterType, gatekeeper } from "./Gatekeeper";
|
||||
|
||||
const config = getServerConfig();
|
||||
|
||||
@@ -80,7 +80,7 @@ export function startWorker() {
|
||||
// Endpoint to create a private lobby
|
||||
app.post(
|
||||
"/create_game/:id",
|
||||
securityMiddleware.httpHandler(async (req, res) => {
|
||||
gatekeeper.httpHandler(LimiterType.Post, async (req, res) => {
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
console.warn(`cannot create game, id not found`);
|
||||
@@ -111,13 +111,13 @@ export function startWorker() {
|
||||
`Worker ${workerId}: IP ${clientIP} creating game ${game.isPublic() ? "Public" : "Private"} with id ${id}`,
|
||||
);
|
||||
res.json(game.gameInfo());
|
||||
}, LimiterType.Post),
|
||||
}),
|
||||
);
|
||||
|
||||
// Add other endpoints from your original server
|
||||
app.post(
|
||||
"/start_game/:id",
|
||||
securityMiddleware.httpHandler(async (req, res) => {
|
||||
gatekeeper.httpHandler(LimiterType.Post, async (req, res) => {
|
||||
console.log(`starting private lobby with id ${req.params.id}`);
|
||||
const game = gm.game(req.params.id);
|
||||
if (!game) {
|
||||
@@ -132,12 +132,12 @@ export function startWorker() {
|
||||
}
|
||||
game.start();
|
||||
res.status(200).json({ success: true });
|
||||
}, LimiterType.Post),
|
||||
}),
|
||||
);
|
||||
|
||||
app.put(
|
||||
"/game/:id",
|
||||
securityMiddleware.httpHandler(async (req, res) => {
|
||||
gatekeeper.httpHandler(LimiterType.Put, async (req, res) => {
|
||||
// TODO: only update public game if from local host
|
||||
const lobbyID = req.params.id;
|
||||
if (req.body.gameType == GameType.Public) {
|
||||
@@ -163,34 +163,34 @@ export function startWorker() {
|
||||
disableNPCs: req.body.disableNPCs,
|
||||
});
|
||||
res.status(200).json({ success: true });
|
||||
}, LimiterType.Put),
|
||||
}),
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/game/:id/exists",
|
||||
securityMiddleware.httpHandler(async (req, res) => {
|
||||
gatekeeper.httpHandler(LimiterType.Get, async (req, res) => {
|
||||
const lobbyId = req.params.id;
|
||||
res.json({
|
||||
exists: gm.game(lobbyId) != null,
|
||||
});
|
||||
}, LimiterType.Get),
|
||||
}),
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/game/:id",
|
||||
securityMiddleware.httpHandler(async (req, res) => {
|
||||
gatekeeper.httpHandler(LimiterType.Get, async (req, res) => {
|
||||
const game = gm.game(req.params.id);
|
||||
if (game == null) {
|
||||
console.log(`lobby ${req.params.id} not found`);
|
||||
return res.status(404).json({ error: "Game not found" });
|
||||
}
|
||||
res.json(game.gameInfo());
|
||||
}, LimiterType.Get),
|
||||
}),
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/archive_singleplayer_game",
|
||||
securityMiddleware.httpHandler(async (req, res) => {
|
||||
gatekeeper.httpHandler(LimiterType.Post, async (req, res) => {
|
||||
const gameRecord: GameRecord = req.body;
|
||||
const clientIP = req.ip || req.socket.remoteAddress || "unknown";
|
||||
|
||||
@@ -204,14 +204,14 @@ export function startWorker() {
|
||||
res.json({
|
||||
success: true,
|
||||
});
|
||||
}, LimiterType.Post),
|
||||
}),
|
||||
);
|
||||
|
||||
// WebSocket handling
|
||||
wss.on("connection", (ws: WebSocket, req) => {
|
||||
ws.on(
|
||||
"message",
|
||||
securityMiddleware.wsHandler(req, async (message: string) => {
|
||||
gatekeeper.wsHandler(req, async (message: string) => {
|
||||
const forwarded = req.headers["x-forwarded-for"];
|
||||
const ip = Array.isArray(forwarded)
|
||||
? forwarded[0]
|
||||
|
||||
+1
-1
Submodule src/server/gatekeeper updated: 3192c20cde...392e2b70af
Reference in New Issue
Block a user