Files
OpenFrontIO/src/server/jwt.ts
T
2025-12-12 16:49:22 -08:00

101 lines
2.5 KiB
TypeScript

import { jwtVerify } from "jose";
import { z } from "zod";
import {
TokenPayload,
TokenPayloadSchema,
UserMeResponse,
UserMeResponseSchema,
} from "../core/ApiSchemas";
import { GameEnv, ServerConfig } from "../core/configuration/Config";
import { PersistentIdSchema } from "../core/Schemas";
type TokenVerificationResult =
| {
type: "success";
persistentId: string;
claims: TokenPayload | null;
}
| { type: "error"; message: string };
export async function verifyClientToken(
token: string,
config: ServerConfig,
): Promise<TokenVerificationResult> {
if (PersistentIdSchema.safeParse(token).success) {
if (config.env() === GameEnv.Dev) {
return { type: "success", persistentId: token, claims: null };
} else {
return {
type: "error",
message: "persistent ID not allowed in production",
};
}
}
try {
const issuer = config.jwtIssuer();
const audience = config.jwtAudience();
const key = await config.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,
config: ServerConfig,
): Promise<
| { type: "success"; response: UserMeResponse }
| { type: "error"; message: string }
> {
try {
// Get the user object
const response = await fetch(config.jwtIssuer() + "/users/@me", {
headers: {
authorization: `Bearer ${token}`,
},
});
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}`,
};
}
}