diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 285c704a7..bc6903bf5 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -144,7 +144,7 @@ const SafeString = z ) .max(1000); -const PersistentIdSchema = z.string().uuid(); +export const PersistentIdSchema = z.string().uuid(); const JwtTokenSchema = z.string().jwt(); const TokenSchema = z .string() diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 57fced752..f08dcc5e8 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -193,7 +193,7 @@ export class GameServer { this.log.error("Failed to parse client message", error, { clientID: client.clientID, }); - client.ws.close(); + client.ws.close(1002, "ClientMessageSchema"); return; } const clientMsg = parsed.data; @@ -253,7 +253,7 @@ export class GameServer { client.ws.removeAllListeners("error"); client.ws.on("error", (error: Error) => { if ((error as any).code === "WS_ERR_UNEXPECTED_RSV_1") { - client.ws.close(1002); + client.ws.close(1002, "WS_ERR_UNEXPECTED_RSV_1"); } }); diff --git a/src/server/Worker.ts b/src/server/Worker.ts index 5bee6b603..2dd8f1490 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -300,7 +300,7 @@ export function startWorker() { if (!parsed.success) { const error = z.prettifyError(parsed.error); log.warn("Error parsing join message client", error); - ws.close(); + ws.close(1002, "ClientJoinMessageSchema"); return; } const clientMsg = parsed.data; @@ -315,18 +315,24 @@ export function startWorker() { return; } - const { persistentId, claims } = await verifyClientToken( - clientMsg.token, - config, - ); + const result = await verifyClientToken(clientMsg.token, config); + if (result === false) { + log.warn("Failed to verify token"); + ws.close(1002, "Failed to verify token"); + return; + } + const { persistentId, claims } = result; let roles: string[] | undefined; - // Check user roles - if (claims !== null) { + if (claims === null) { + // TODO: Verify that the persistendId is is not a registered player + } else { + // Verify token and get player permissions const result = await getUserMe(clientMsg.token, config); if (result === false) { log.warn("Token is not valid", claims); + ws.close(1002, "Token is not valid"); return; } roles = result.player.roles; @@ -374,7 +380,7 @@ export function startWorker() { ws.on("error", (error: Error) => { if ((error as any).code === "WS_ERR_UNEXPECTED_RSV_1") { - ws.close(1002); + ws.close(1002, "WS_ERR_UNEXPECTED_RSV_1"); } }); }); diff --git a/src/server/jwt.ts b/src/server/jwt.ts index c7896bd91..d056b8445 100644 --- a/src/server/jwt.ts +++ b/src/server/jwt.ts @@ -6,31 +6,38 @@ import { UserMeResponseSchema, } from "../core/ApiSchemas"; import { ServerConfig } from "../core/configuration/Config"; +import { PersistentIdSchema } from "../core/Schemas"; -type TokenVerificationResult = { - persistentId: string; - claims: TokenPayload | null; -}; +type TokenVerificationResult = + | { + persistentId: string; + claims: TokenPayload | null; + } + | false; export async function verifyClientToken( token: string, config: ServerConfig, ): Promise { - if (token.length === 36) { + if (PersistentIdSchema.safeParse(token).success) { return { persistentId: token, claims: null }; } - const issuer = config.jwtIssuer(); - const audience = config.jwtAudience(); - const key = await config.jwkPublicKey(); - const { payload, protectedHeader } = await jwtVerify(token, key, { - algorithms: ["EdDSA"], - issuer, - audience, - maxTokenAge: "6 days", - }); - const claims = TokenPayloadSchema.parse(payload); - const persistentId = claims.sub; - return { persistentId, claims }; + try { + const issuer = config.jwtIssuer(); + const audience = config.jwtAudience(); + const key = await config.jwkPublicKey(); + const { payload, protectedHeader } = await jwtVerify(token, key, { + algorithms: ["EdDSA"], + issuer, + audience, + maxTokenAge: "6 days", + }); + const claims = TokenPayloadSchema.parse(payload); + const persistentId = claims.sub; + return { persistentId, claims }; + } catch (e) { + return false; + } } export async function getUserMe(