mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-10 18:54:36 +00:00
43d07ca85f
Tighten the package model around the determinism invariant: everything the
engine imports must itself be deterministic.
- Rename core-public -> engine-public (package, dir, `engine-public/*` alias,
all 118 import sites). It is the deterministic public surface the engine
depends on and that client/server also use.
- Move the pure formatting helpers (renderNumber/renderTroops) from shared
into engine-public/format — the engine uses them and they're deterministic.
- Drop the now-empty shared package (and its alias). `shared` was intended for
client/server-shared, engine-forbidden code; there is none yet, so it is
removed and can be re-added when such code appears.
Final graph: engine-public (leaf) <- engine <- {client, server};
client/server also -> engine-public.
Verified: tsc clean; 1364 + 65 tests pass; prod build succeeds; engine-public
is a leaf; engine has no client/server imports. Lockfile regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
2.6 KiB
TypeScript
101 lines
2.6 KiB
TypeScript
import { jwtVerify } from "jose";
|
|
import { z } from "zod";
|
|
import {
|
|
TokenPayload,
|
|
TokenPayloadSchema,
|
|
UserMeResponse,
|
|
UserMeResponseSchema,
|
|
} from "engine-public/ApiSchemas";
|
|
import { GameEnv } from "engine/configuration/Config";
|
|
import { PersistentIdSchema } from "engine-public/Schemas";
|
|
import { ServerEnv } from "./ServerEnv";
|
|
|
|
type TokenVerificationResult =
|
|
| {
|
|
type: "success";
|
|
persistentId: string;
|
|
claims: TokenPayload | null;
|
|
}
|
|
| { type: "error"; message: string };
|
|
|
|
export async function verifyClientToken(
|
|
token: string,
|
|
): Promise<TokenVerificationResult> {
|
|
if (PersistentIdSchema.safeParse(token).success) {
|
|
if (ServerEnv.env() === GameEnv.Dev) {
|
|
return { type: "success", persistentId: token, claims: null };
|
|
} else {
|
|
return {
|
|
type: "error",
|
|
message: "persistent ID not allowed in production",
|
|
};
|
|
}
|
|
}
|
|
try {
|
|
const issuer = ServerEnv.jwtIssuer();
|
|
const audience = ServerEnv.jwtAudience();
|
|
const key = await ServerEnv.jwkPublicKey();
|
|
const { payload } = await jwtVerify(token, key, {
|
|
algorithms: ["EdDSA"],
|
|
issuer,
|
|
audience,
|
|
});
|
|
const result = TokenPayloadSchema.safeParse(payload);
|
|
if (!result.success) {
|
|
return {
|
|
type: "error",
|
|
message: z.prettifyError(result.error),
|
|
};
|
|
}
|
|
const claims = result.data;
|
|
const persistentId = claims.sub;
|
|
return { type: "success", persistentId, claims };
|
|
} catch (e) {
|
|
const message =
|
|
e instanceof Error
|
|
? e.message
|
|
: typeof e === "string"
|
|
? e
|
|
: "An unknown error occurred";
|
|
|
|
return { type: "error", message };
|
|
}
|
|
}
|
|
|
|
export async function getUserMe(
|
|
token: string,
|
|
): Promise<
|
|
| { type: "success"; response: UserMeResponse }
|
|
| { type: "error"; message: string }
|
|
> {
|
|
try {
|
|
// Get the user object
|
|
const response = await fetch(ServerEnv.jwtIssuer() + "/users/@me", {
|
|
headers: {
|
|
authorization: `Bearer ${token}`,
|
|
"x-api-key": ServerEnv.apiKey(),
|
|
},
|
|
});
|
|
if (response.status !== 200) {
|
|
return {
|
|
type: "error",
|
|
message: `Failed to fetch user me: ${response.statusText}`,
|
|
};
|
|
}
|
|
const body = await response.json();
|
|
const result = UserMeResponseSchema.safeParse(body);
|
|
if (!result.success) {
|
|
return {
|
|
type: "error",
|
|
message: `Invalid response: ${z.prettifyError(result.error)}`,
|
|
};
|
|
}
|
|
return { type: "success", response: result.data };
|
|
} catch (e) {
|
|
return {
|
|
type: "error",
|
|
message: `Failed to fetch user me: ${e}`,
|
|
};
|
|
}
|
|
}
|