Files
OpenFrontIO/src/server/Privilege.ts
T
evanpelle 00668dd924 Remove role based perms, fetch cosmetics.json from api (#1640)
## Description:

* Fetch cosmetics.json from api
* Remove all role based perms, we are only using flares now
* Created Priviledge refresher which periodically polls /cosmetics.json
endpoint.

## 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
2025-08-04 16:48:41 -07:00

150 lines
4.1 KiB
TypeScript

import { Cosmetics, Pattern } from "../core/CosmeticSchemas";
import { PatternDecoder } from "../core/PatternDecoder";
export interface PrivilegeChecker {
isPatternAllowed(
base64: string,
flares: readonly string[] | undefined,
): true | "restricted" | "unlisted" | "invalid";
isCustomFlagAllowed(
flag: string,
flares: readonly string[] | undefined,
): true | "restricted" | "invalid";
}
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,
flares: readonly string[] | undefined,
): true | "restricted" | "unlisted" | "invalid" {
// 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";
}
if (
flares !== undefined &&
(flares.includes(`pattern:${found.name}`) || flares.includes("pattern:*"))
) {
// Player has a flare for this pattern
return true;
}
return "restricted";
}
isCustomFlagAllowed(
flag: string,
flares: readonly string[] | undefined,
): true | "restricted" | "invalid" {
if (!flag.startsWith("!")) return "invalid";
const code = flag.slice(1);
if (!code) return "invalid";
const segments = code.split("_");
if (segments.length === 0) return "invalid";
const MAX_LAYERS = 6; // Maximum number of layers allowed
if (segments.length > MAX_LAYERS) return "invalid";
const superFlare = flares?.includes("flag:*") ?? false;
for (const segment of segments) {
const [layerKey, colorKey] = segment.split("-");
if (!layerKey || !colorKey) return "invalid";
const layer = this.cosmetics.flag?.layers[layerKey];
const color = this.cosmetics.flag?.color[colorKey];
if (!layer || !color) return "invalid";
// Super-flare bypasses all restrictions
if (superFlare) {
continue;
}
// Check layer restrictions
const layerSpec = layer;
let layerAllowed = false;
if (!layerSpec.flares) {
layerAllowed = true;
} else {
// By flare
if (
layerSpec.flares &&
flares?.some((f) => layerSpec.flares?.includes(f))
) {
layerAllowed = true;
}
// By named flag:layer:{name}
if (flares?.includes(`flag:layer:${layerSpec.name}`)) {
layerAllowed = true;
}
}
// Check color restrictions
const colorSpec = color;
let colorAllowed = false;
if (!colorSpec.flares) {
colorAllowed = true;
} else {
// By flare
if (
colorSpec.flares &&
flares?.some((f) => colorSpec.flares?.includes(f))
) {
colorAllowed = true;
}
// By named flag:color:{name}
if (flares?.includes(`flag:color:${colorSpec.name}`)) {
colorAllowed = true;
}
}
// If either part is restricted, block
if (!(layerAllowed && colorAllowed)) {
return "restricted";
}
}
return true;
}
}
export class FailOpenPrivilegeChecker implements PrivilegeChecker {
isPatternAllowed(
name: string,
flares: readonly string[] | undefined,
): true | "restricted" | "unlisted" | "invalid" {
return true;
}
isCustomFlagAllowed(
flag: string,
flares: readonly string[] | undefined,
): true | "restricted" | "invalid" {
return true;
}
}