mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-27 11:14:16 +00:00
store & reference pattern by name (#1766)
## Description: Store pattern by name instead of value. The worker replaces the pattern name with it's base64 when joining. This ensures the client & server are never out of sync after patterns are updated. * removed resizeObserver on the territory modal, it was causing some race conditions, and the modal is not resizable so it's unnecessary. * Moved PatternSchema to CosmeticSchema ## 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 have read and accepted the CLA agreement (only required once). ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
+24
-34
@@ -1,11 +1,16 @@
|
||||
import { Cosmetics, Pattern } from "../core/CosmeticSchemas";
|
||||
import { Cosmetics } from "../core/CosmeticSchemas";
|
||||
import { PatternDecoder } from "../core/PatternDecoder";
|
||||
|
||||
type PatternResult =
|
||||
| { type: "allowed"; pattern: string }
|
||||
| { type: "unknown" }
|
||||
| { type: "forbidden"; reason: string };
|
||||
|
||||
export interface PrivilegeChecker {
|
||||
isPatternAllowed(
|
||||
base64: string,
|
||||
flares: readonly string[] | undefined,
|
||||
): true | "restricted" | "unlisted" | "invalid";
|
||||
): PatternResult;
|
||||
isCustomFlagAllowed(
|
||||
flag: string,
|
||||
flares: readonly string[] | undefined,
|
||||
@@ -13,49 +18,34 @@ export interface PrivilegeChecker {
|
||||
}
|
||||
|
||||
export class PrivilegeCheckerImpl implements PrivilegeChecker {
|
||||
private b64ToPattern: Record<string, Pattern> = {};
|
||||
|
||||
constructor(
|
||||
private cosmetics: Cosmetics,
|
||||
private b64urlDecode: (base64: string) => Uint8Array,
|
||||
) {
|
||||
for (const name in this.cosmetics.patterns) {
|
||||
const pattern = this.cosmetics.patterns[name];
|
||||
this.b64ToPattern[pattern.pattern] = pattern;
|
||||
}
|
||||
}
|
||||
) {}
|
||||
|
||||
isPatternAllowed(
|
||||
base64: string,
|
||||
name: string,
|
||||
flares: readonly string[] | undefined,
|
||||
): true | "restricted" | "unlisted" | "invalid" {
|
||||
): PatternResult {
|
||||
// Look for the pattern in the cosmetics.json config
|
||||
const found = this.b64ToPattern[base64];
|
||||
if (found === undefined) {
|
||||
try {
|
||||
// Ensure that the pattern will not throw for clients
|
||||
new PatternDecoder(base64, this.b64urlDecode);
|
||||
} catch (e) {
|
||||
// Pattern is invalid
|
||||
return "invalid";
|
||||
}
|
||||
// Pattern is unlisted
|
||||
if (flares !== undefined && flares.includes("pattern:*")) {
|
||||
// Player has the super-flare
|
||||
return true;
|
||||
}
|
||||
return "unlisted";
|
||||
const found = this.cosmetics.patterns[name];
|
||||
if (!found) return { type: "forbidden", reason: "pattern not found" };
|
||||
|
||||
try {
|
||||
new PatternDecoder(found.pattern, this.b64urlDecode);
|
||||
} catch (e) {
|
||||
return { type: "forbidden", reason: "invalid pattern" };
|
||||
}
|
||||
|
||||
if (
|
||||
flares !== undefined &&
|
||||
(flares.includes(`pattern:${found.name}`) || flares.includes("pattern:*"))
|
||||
flares?.includes(`pattern:${found.name}`) ||
|
||||
flares?.includes("pattern:*")
|
||||
) {
|
||||
// Player has a flare for this pattern
|
||||
return true;
|
||||
return { type: "allowed", pattern: found.pattern };
|
||||
} else {
|
||||
return { type: "forbidden", reason: "no flares for pattern" };
|
||||
}
|
||||
|
||||
return "restricted";
|
||||
}
|
||||
|
||||
isCustomFlagAllowed(
|
||||
@@ -136,8 +126,8 @@ export class FailOpenPrivilegeChecker implements PrivilegeChecker {
|
||||
isPatternAllowed(
|
||||
name: string,
|
||||
flares: readonly string[] | undefined,
|
||||
): true | "restricted" | "unlisted" | "invalid" {
|
||||
return true;
|
||||
): PatternResult {
|
||||
return { type: "unknown" };
|
||||
}
|
||||
|
||||
isCustomFlagAllowed(
|
||||
|
||||
+23
-8
@@ -24,6 +24,7 @@ import { gatekeeper, LimiterType } from "./Gatekeeper";
|
||||
import { getUserMe, verifyClientToken } from "./jwt";
|
||||
import { logger } from "./Logger";
|
||||
|
||||
import { assertNever } from "../core/Util";
|
||||
import { PrivilegeRefresher } from "./PrivilegeRefresher";
|
||||
import { initWorkerMetrics } from "./WorkerMetrics";
|
||||
|
||||
@@ -410,15 +411,29 @@ export async function startWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
let pattern: string | undefined;
|
||||
// Check if the pattern is allowed
|
||||
if (clientMsg.pattern !== undefined) {
|
||||
const allowed = privilegeRefresher
|
||||
if (clientMsg.patternName !== undefined) {
|
||||
const result = privilegeRefresher
|
||||
.get()
|
||||
.isPatternAllowed(clientMsg.pattern, flares);
|
||||
if (allowed !== true) {
|
||||
log.warn(`Pattern ${allowed}: ${clientMsg.pattern}`);
|
||||
ws.close(1002, `Pattern ${allowed}`);
|
||||
return;
|
||||
.isPatternAllowed(clientMsg.patternName, flares);
|
||||
switch (result.type) {
|
||||
case "allowed":
|
||||
pattern = result.pattern;
|
||||
break;
|
||||
case "unknown":
|
||||
// Api could be down, so allow player to join but disable pattern.
|
||||
log.warn(`Pattern ${clientMsg.patternName} unknown`);
|
||||
break;
|
||||
case "forbidden":
|
||||
log.warn(`Pattern ${clientMsg.patternName}: ${result.reason}`);
|
||||
ws.close(
|
||||
1002,
|
||||
`Pattern ${clientMsg.patternName}: ${result.reason}`,
|
||||
);
|
||||
return;
|
||||
default:
|
||||
assertNever(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +448,7 @@ export async function startWorker() {
|
||||
clientMsg.username,
|
||||
ws,
|
||||
clientMsg.flag,
|
||||
clientMsg.pattern,
|
||||
pattern,
|
||||
);
|
||||
|
||||
const wasFound = gm.addClient(
|
||||
|
||||
Reference in New Issue
Block a user