mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-01 17:33:26 +00:00
Patterned territory (#786)
## Description: This is meant to give players more customization options. Permission handling hasn’t really been implemented yet. ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: aotumuri
This commit is contained in:
@@ -14,9 +14,11 @@ export class Client {
|
||||
public readonly persistentID: string,
|
||||
public readonly claims: TokenPayload | null,
|
||||
public readonly roles: string[] | undefined,
|
||||
public readonly flares: string[] | undefined,
|
||||
public readonly ip: string,
|
||||
public readonly username: string,
|
||||
public readonly ws: WebSocket,
|
||||
public readonly flag: string | undefined,
|
||||
public readonly pattern: string | undefined,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -333,6 +333,7 @@ export class GameServer {
|
||||
players: this.activeClients.map((c) => ({
|
||||
username: c.username,
|
||||
clientID: c.clientID,
|
||||
pattern: c.pattern,
|
||||
flag: c.flag,
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { PatternDecoder } from "../core/Cosmetics";
|
||||
import { Cosmetics } from "../core/CosmeticSchemas";
|
||||
type PatternEntry = {
|
||||
pattern: string;
|
||||
role_group?: string[];
|
||||
};
|
||||
export class PrivilegeChecker {
|
||||
constructor(private cosmetics: Cosmetics) {}
|
||||
|
||||
isPatternAllowed(
|
||||
base64: string,
|
||||
roles: readonly string[] | undefined,
|
||||
flares: readonly string[] | undefined,
|
||||
): true | "restricted" | "unlisted" | "invalid" {
|
||||
// Look for the pattern in the cosmetics.json config
|
||||
let found: [string, PatternEntry] | undefined;
|
||||
for (const key in this.cosmetics.pattern) {
|
||||
const entry = this.cosmetics.pattern[key];
|
||||
if (entry.pattern === base64) {
|
||||
found = [key, entry];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
try {
|
||||
// Ensure that the pattern will not throw for clients
|
||||
new PatternDecoder(base64);
|
||||
} catch (e) {
|
||||
// Pattern is invalid
|
||||
return "invalid";
|
||||
}
|
||||
// Pattern is unlisted
|
||||
if (flares !== undefined && flares.includes("pattern:*")) {
|
||||
return true;
|
||||
}
|
||||
return "unlisted";
|
||||
}
|
||||
|
||||
const [key, entry] = found;
|
||||
const allowedGroups = entry.role_group;
|
||||
|
||||
if (allowedGroups === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const groupName of allowedGroups) {
|
||||
const groupRoles = this.cosmetics.role_group?.[groupName] || [];
|
||||
if (
|
||||
roles !== undefined &&
|
||||
roles.some((role) => groupRoles.includes(role))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
flares !== undefined &&
|
||||
(flares.includes(`pattern:${key}`) || flares.includes("pattern:*"))
|
||||
)
|
||||
return true;
|
||||
|
||||
return "restricted";
|
||||
}
|
||||
}
|
||||
+27
-1
@@ -8,6 +8,7 @@ import { WebSocket, WebSocketServer } from "ws";
|
||||
import { z } from "zod/v4";
|
||||
import { GameEnv } from "../core/configuration/Config";
|
||||
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
||||
import { territoryPatterns } from "../core/Cosmetics";
|
||||
import { GameType } from "../core/game/Game";
|
||||
import {
|
||||
ClientJoinMessageSchema,
|
||||
@@ -22,6 +23,7 @@ import { GameManager } from "./GameManager";
|
||||
import { gatekeeper, LimiterType } from "./Gatekeeper";
|
||||
import { getUserMe, verifyClientToken } from "./jwt";
|
||||
import { logger } from "./Logger";
|
||||
import { PrivilegeChecker } from "./Privilege";
|
||||
import { initWorkerMetrics } from "./WorkerMetrics";
|
||||
|
||||
const config = getServerConfigFromServer();
|
||||
@@ -29,6 +31,8 @@ const config = getServerConfigFromServer();
|
||||
const workerId = parseInt(process.env.WORKER_ID || "0");
|
||||
const log = logger.child({ comp: `w_${workerId}` });
|
||||
|
||||
const privilegeChecker = new PrivilegeChecker(territoryPatterns);
|
||||
|
||||
// Worker setup
|
||||
export function startWorker() {
|
||||
log.info(`Worker starting...`);
|
||||
@@ -321,6 +325,7 @@ export function startWorker() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify token signature
|
||||
const result = await verifyClientToken(clientMsg.token, config);
|
||||
if (result === false) {
|
||||
log.warn("Failed to verify token");
|
||||
@@ -330,6 +335,7 @@ export function startWorker() {
|
||||
const { persistentId, claims } = result;
|
||||
|
||||
let roles: string[] | undefined;
|
||||
let flares: string[] | undefined;
|
||||
|
||||
if (claims === null) {
|
||||
// TODO: Verify that the persistendId is is not a registered player
|
||||
@@ -342,9 +348,27 @@ export function startWorker() {
|
||||
return;
|
||||
}
|
||||
roles = result.player.roles;
|
||||
flares = result.player.flares;
|
||||
}
|
||||
|
||||
// TODO: Validate client settings based on roles
|
||||
// Check if the flag is allowed
|
||||
if (clientMsg.flag !== undefined) {
|
||||
// TODO: Implement custom flag validation
|
||||
}
|
||||
|
||||
// Check if the pattern is allowed
|
||||
if (clientMsg.pattern !== undefined) {
|
||||
const allowed = privilegeChecker.isPatternAllowed(
|
||||
clientMsg.pattern,
|
||||
roles,
|
||||
flares,
|
||||
);
|
||||
if (allowed !== true) {
|
||||
log.warn(`Pattern ${allowed}: ${clientMsg.pattern}`);
|
||||
ws.close(1002, `Pattern ${allowed}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create client and add to game
|
||||
const client = new Client(
|
||||
@@ -352,10 +376,12 @@ export function startWorker() {
|
||||
persistentId,
|
||||
claims,
|
||||
roles,
|
||||
flares,
|
||||
ip,
|
||||
clientMsg.username,
|
||||
ws,
|
||||
clientMsg.flag,
|
||||
clientMsg.pattern,
|
||||
);
|
||||
|
||||
const wasFound = gm.addClient(
|
||||
|
||||
Reference in New Issue
Block a user