diff --git a/apps/verify-server/package.json b/apps/verify-server/package.json index 5503fdbc0..1b112fa5d 100644 --- a/apps/verify-server/package.json +++ b/apps/verify-server/package.json @@ -19,7 +19,7 @@ "@statsify/util": "workspace:^", "@swc/helpers": "^0.5.23", "@typegoose/typegoose": "^12.6.0", - "minecraft-protocol": "^1.61.0", + "minecraft-protocol": "^1.66.1", "mongoose": "^8.5.2" } } diff --git a/apps/verify-server/src/index.ts b/apps/verify-server/src/index.ts index 0ce9286a3..6db57c8a3 100644 --- a/apps/verify-server/src/index.ts +++ b/apps/verify-server/src/index.ts @@ -8,16 +8,28 @@ import * as Sentry from "@sentry/node"; import { Logger } from "@statsify/logger"; +import { + type ServerClient, + createServer, + states, +} from "minecraft-protocol"; import { UserLogo, VerifyCode } from "@statsify/schemas"; import { config, formatTime } from "@statsify/util"; import { connect } from "mongoose"; -import { createServer } from "minecraft-protocol"; +import { createRequire } from "node:module"; import { generateCode } from "./generate-code.js"; import { getLogoPath } from "@statsify/assets"; import { getModelForClass } from "@typegoose/typegoose"; import { readFileSync } from "node:fs"; const logger = new Logger("verify-server"); +const require = createRequire(import.meta.url); +const minecraftProtocolRequire = createRequire(require.resolve("minecraft-protocol")); +const minecraftData = minecraftProtocolRequire("minecraft-data") as ( + protocolVersion: number +) => unknown; +const supportedVersions = minecraftProtocolRequire("minecraft-protocol") + .supportedVersions as string[]; const handleError = logger.error.bind(logger); @@ -35,6 +47,26 @@ const codeCreatedMessage = (code: string, time: Date) => { )}§r§7.`; }; +const unsupportedProtocolMessage = (protocolVersion: number) => + `§9§lStatsify Verification Server\n\n§r§7Your Minecraft version is not supported yet.\n\n§r§7Please try again with §c§lMinecraft ${ + supportedVersions.at(-1) ?? "a supported version" + }§r§7 or older.\n\n§r§8Protocol ${protocolVersion}`; + +const supportsFeature = (client: ServerClient, feature: string) => { + const supportFeature = (client as ServerClient & { + supportFeature?: (feature: string) => boolean; + }).supportFeature; + + return supportFeature?.(feature) ?? false; +}; + +const clientDetails = (client: ServerClient) => + `id=${client.id} username=${client.username ?? "unknown"} uuid=${ + client.uuid ?? "unknown" + } version=${client.version} protocol=${client.protocolVersion} state=${client.state}`; + +const isSupportedProtocol = (protocolVersion: number) => Boolean(minecraftData(protocolVersion)); + const sentryDsn = await config("sentry.verifyServerDsn", { required: false }); if (sentryDsn) { @@ -74,23 +106,87 @@ const server = createServer({ logger.log("Server Started"); -server.on("login", async (client) => { +server.on("connection", (client) => { + logger.verbose(`Connection opened: ${clientDetails(client)}`); + + client.prependOnceListener("set_protocol", (packet: { + nextState: number; + protocolVersion: number; + }) => { + if (packet.nextState !== 2 || isSupportedProtocol(packet.protocolVersion)) return; + + logger.warn( + `Unsupported Minecraft protocol attempted login: ` + + `protocol=${packet.protocolVersion} ${clientDetails(client)}` + ); + + // minecraft-protocol ends unsupported handshakes before setting LOGIN state. + // Moving to LOGIN first lets the client receive a visible disconnect packet + // instead of continuing into mismatched configuration registry data. + client.state = states.LOGIN; + client.removeAllListeners("login_start"); + + const end = client.end.bind(client); + + client.end = (reason?: string) => { + if (reason === `Protocol version ${packet.protocolVersion} is not supported`) { + return end(unsupportedProtocolMessage(packet.protocolVersion)); + } + + return end(reason); + }; + }); + + client.on("state", (newState, oldState) => { + logger.verbose(`Connection state changed: ${clientDetails(client)} ${oldState} -> ${newState}`); + }); + + client.on("error", (error) => { + logger.error(`Connection error: ${clientDetails(client)}`); + logger.error(error); + }); + + client.on("end", (reason) => { + logger.verbose(`Connection ended: ${clientDetails(client)} reason=${reason ?? "unknown"}`); + }); +}); + +server.on("playerJoin", async (client) => { try { - logger.verbose(`${client.username} [${client.uuid}] has joined`); + logger.verbose( + `${client.username} [${client.uuid}] has joined: ${clientDetails(client)} ` + + `hasConfigurationState=${supportsFeature(client, "hasConfigurationState")} ` + + `chatPacketsUseNbtComponents=${supportsFeature(client, "chatPacketsUseNbtComponents")}` + ); const uuid = client.uuid.replaceAll("-", ""); const previousVerifyCode = await verifyCodesModel.findOne({ uuid }).lean().exec(); - if (previousVerifyCode) - return client.end( - codeCreatedMessage(previousVerifyCode.code, previousVerifyCode.expireAt) + if (previousVerifyCode) { + const message = codeCreatedMessage( + previousVerifyCode.code, + previousVerifyCode.expireAt + ); + + logger.verbose( + `Disconnecting existing verification code client: ${clientDetails(client)} ` + + `messageLength=${message.length}` ); + return client.end(message); + } + const code = await generateCode(verifyCodesModel); const verifyCode = await verifyCodesModel.create(new VerifyCode(uuid, code)); + const message = codeCreatedMessage(verifyCode.code, verifyCode.expireAt); + + logger.verbose( + `Disconnecting new verification code client: ${clientDetails(client)} ` + + `messageLength=${message.length}` + ); - client.end(codeCreatedMessage(verifyCode.code, verifyCode.expireAt)); + client.end(message); logger.verbose(`${client.username} has been assigned to the code ${verifyCode.code}`); } catch (error) { logger.error(error); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03e8e2a0f..0e19da505 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,7 +43,7 @@ importers: version: 0.53.0 oxlint: specifier: ^1.68.0 - version: 1.68.0 + version: 1.68.0(oxlint-tsgolint@0.23.0) pm2: specifier: ^6.0.8 version: 6.0.8 @@ -434,8 +434,8 @@ importers: specifier: ^12.6.0 version: 12.19.0(mongoose@8.18.0(socks@2.8.7)) minecraft-protocol: - specifier: ^1.61.0 - version: 1.61.0 + specifier: ^1.66.1 + version: 1.66.2 mongoose: specifier: ^8.5.2 version: 8.18.0(socks@2.8.7) @@ -443,7 +443,7 @@ importers: assets/private: dependencies: skia-canvas: - specifier: 3.0.8 + specifier: ^3.0.8 version: 3.0.8 stackblur-canvas: specifier: ^2.7.0 @@ -2146,6 +2146,36 @@ packages: cpu: [x64] os: [win32] + '@oxlint-tsgolint/darwin-arm64@0.23.0': + resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.23.0': + resolution: {integrity: sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.23.0': + resolution: {integrity: sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.23.0': + resolution: {integrity: sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.23.0': + resolution: {integrity: sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.23.0': + resolution: {integrity: sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==} + cpu: [x64] + os: [win32] + '@oxlint/binding-android-arm-eabi@1.68.0': resolution: {integrity: sha512-wEdsIspexXLLMCPAEOcCuFLMt6aE3AzTuA/nQKLPRnoJ+EQTturmGheDkhHuuVHx0GbutjQ3JKmEn+Gz6Ag28Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3171,11 +3201,9 @@ packages: '@vitest/utils@4.1.8': resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} - '@xboxreplay/errors@0.1.0': - resolution: {integrity: sha512-Tgz1d/OIPDWPeyOvuL5+aai5VCcqObhPnlI3skQuf80GVF3k1I0lPCnGC+8Cm5PV9aLBT5m8qPcJoIUQ2U4y9g==} - - '@xboxreplay/xboxlive-auth@3.3.3': - resolution: {integrity: sha512-j0AU8pW10LM8O68CTZ5QHnvOjSsnPICy0oQcP7zyM7eWkDQ/InkiQiirQKsPn1XRYDl4ccNu0WM582s3UKwcBg==} + '@xboxreplay/xboxlive-auth@5.1.0': + resolution: {integrity: sha512-UngHHsehZbiTjyyNmo8HvdoUDKMID1U9uVfrpFWUK/2UxPuVTKy5n+CzZQ3S488sW5vOhgh0lHqqynT8ouwgvw==} + engines: {node: '>=16.0.0'} '@xhmikosr/archive-type@8.0.1': resolution: {integrity: sha512-toXuiWChyfOpEiCPsIw6HGHaNji5LVkvB6EREL548vGWr+hGaehwxG4LzN20vm9aGFXwnA/Jty8yW2/SmV+1zQ==} @@ -3249,9 +3277,6 @@ packages: ajv: optional: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} @@ -3333,9 +3358,6 @@ packages: avvio@9.1.0: resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} - axios@0.21.4: - resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} - axios@1.11.0: resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} @@ -4547,8 +4569,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-protocol@1.61.0: - resolution: {integrity: sha512-LZgcsdC88HZtuEYve7YENYvO6Xhe80Mf1rYLgz1frkMWAHWSSXfrplWoNKkKYcdVGM4Azc99NvbFjZdSDu4yKg==} + minecraft-protocol@1.66.2: + resolution: {integrity: sha512-keY1IY1E2AeurcekCfcXrg0TDbykGVFiMe1E4wR8QkNtQRieNwfr2xaF3g3vT9ChkwzvENqp3jxgmtFCKSUKPg==} engines: {node: '>=22'} minimatch@10.0.3: @@ -4778,6 +4800,10 @@ packages: vite-plus: optional: true + oxlint-tsgolint@0.23.0: + resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==} + hasBin: true + oxlint@1.68.0: resolution: {integrity: sha512-dXcbq+xsmLrMy6T8d0euf3IYUfLmjHIE11pOxiUSi5LHkFZaYPv568R6sEjcavVpUxoaQe66UBuK4HEi74NxpA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4936,8 +4962,8 @@ packages: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} - prismarine-auth@2.7.0: - resolution: {integrity: sha512-L8wTF6sdtnN6hViPNy+Nx39a8iQBwR5iO92AWCiym5cSXp/92pmnuwnTdcmNDWyqq6zY4hbibVGYhgLA1Ox8sQ==} + prismarine-auth@3.1.1: + resolution: {integrity: sha512-NuNrMGZdoigFKsvi1ZZgAEvNYNuE5qe6lo/tw+bqeNbkhpjHC0u1JNxLEujnfqduXI18e19PvUtWNMDl/gH7yw==} prismarine-biome@1.3.0: resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==} @@ -6998,6 +7024,24 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.53.0': optional: true + '@oxlint-tsgolint/darwin-arm64@0.23.0': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.23.0': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.23.0': + optional: true + + '@oxlint-tsgolint/linux-x64@0.23.0': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.23.0': + optional: true + + '@oxlint-tsgolint/win32-x64@0.23.0': + optional: true + '@oxlint/binding-android-arm-eabi@1.68.0': optional: true @@ -7864,14 +7908,7 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@xboxreplay/errors@0.1.0': {} - - '@xboxreplay/xboxlive-auth@3.3.3(debug@4.4.1)': - dependencies: - '@xboxreplay/errors': 0.1.0 - axios: 0.21.4(debug@4.4.1) - transitivePeerDependencies: - - debug + '@xboxreplay/xboxlive-auth@5.1.0': {} '@xhmikosr/archive-type@8.0.1': dependencies: @@ -7987,13 +8024,6 @@ snapshots: optionalDependencies: ajv: 8.17.1 - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -8072,15 +8102,9 @@ snapshots: '@fastify/error': 4.2.0 fastq: 1.19.1 - axios@0.21.4(debug@4.4.1): - dependencies: - follow-redirects: 1.15.11(debug@4.4.1) - transitivePeerDependencies: - - debug - axios@1.11.0: dependencies: - follow-redirects: 1.15.11 + follow-redirects: 1.15.11(debug@4.3.7) form-data: 4.0.4 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -8726,16 +8750,10 @@ snapshots: flatted@3.4.2: {} - follow-redirects@1.15.11: {} - follow-redirects@1.15.11(debug@4.3.7): optionalDependencies: debug: 4.3.7 - follow-redirects@1.15.11(debug@4.4.1): - optionalDependencies: - debug: 4.4.1 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -8896,7 +8914,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -9072,7 +9090,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.2 + semver: 7.8.1 jwa@1.4.2: dependencies: @@ -9264,20 +9282,20 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-protocol@1.61.0: + minecraft-protocol@1.66.2: dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.21 aes-js: 3.1.2 buffer-equal: 1.0.1 - debug: 4.4.1 + debug: 4.4.3 endian-toggle: 0.0.0 lodash.merge: 4.6.2 minecraft-data: 3.93.0 minecraft-folder-path: 1.2.0 node-fetch: 2.7.0 node-rsa: 0.4.2 - prismarine-auth: 2.7.0 + prismarine-auth: 3.1.1 prismarine-chat: 1.11.0 prismarine-nbt: 2.7.0 prismarine-realms: 1.3.2 @@ -9495,7 +9513,17 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.53.0 '@oxfmt/binding-win32-x64-msvc': 0.53.0 - oxlint@1.68.0: + oxlint-tsgolint@0.23.0: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.23.0 + '@oxlint-tsgolint/darwin-x64': 0.23.0 + '@oxlint-tsgolint/linux-arm64': 0.23.0 + '@oxlint-tsgolint/linux-x64': 0.23.0 + '@oxlint-tsgolint/win32-arm64': 0.23.0 + '@oxlint-tsgolint/win32-x64': 0.23.0 + optional: true + + oxlint@1.68.0(oxlint-tsgolint@0.23.0): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.68.0 '@oxlint/binding-android-arm64': 1.68.0 @@ -9516,6 +9544,7 @@ snapshots: '@oxlint/binding-win32-arm64-msvc': 1.68.0 '@oxlint/binding-win32-ia32-msvc': 1.68.0 '@oxlint/binding-win32-x64-msvc': 1.68.0 + oxlint-tsgolint: 0.23.0 p-cancelable@4.0.1: {} @@ -9712,11 +9741,11 @@ snapshots: dependencies: parse-ms: 4.0.0 - prismarine-auth@2.7.0: + prismarine-auth@3.1.1: dependencies: '@azure/msal-node': 2.16.2 - '@xboxreplay/xboxlive-auth': 3.3.3(debug@4.4.1) - debug: 4.4.1 + '@xboxreplay/xboxlive-auth': 5.1.0 + debug: 4.4.3 smart-buffer: 4.2.0 uuid-1345: 1.0.2 transitivePeerDependencies: @@ -9753,7 +9782,7 @@ snapshots: prismarine-realms@1.3.2: dependencies: - debug: 4.4.1 + debug: 4.4.3 node-fetch: 2.7.0 transitivePeerDependencies: - encoding @@ -9777,7 +9806,7 @@ snapshots: protodef-validator@1.4.0: dependencies: - ajv: 6.12.6 + ajv: 6.15.0 protodef@1.19.0: dependencies: @@ -9879,7 +9908,7 @@ snapshots: require-in-the-middle@5.2.0: dependencies: - debug: 4.4.1 + debug: 4.4.3 module-details-from-path: 1.0.4 resolve: 1.22.10 transitivePeerDependencies: @@ -10050,7 +10079,7 @@ snapshots: skia-canvas@3.0.8: dependencies: detect-libc: 2.1.2 - follow-redirects: 1.15.11 + follow-redirects: 1.15.11(debug@4.3.7) https-proxy-agent: 7.0.6 string-split-by: 1.0.0 transitivePeerDependencies: