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 { 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}`, }; } }