From 9107ca0bd6a7d1aee919a9aec8ecf73ff79c5608 Mon Sep 17 00:00:00 2001 From: creeper123123321 <7974274+creeper123123321@users.noreply.github.com> Date: Sun, 18 Apr 2021 09:06:09 -0300 Subject: [PATCH] use completablefuture with coroutines, autodetector fallback to -1 --- README.md | 2 +- build.gradle.kts | 3 +- .../autoprotocol/ProtocolDetectorHandler.kt | 7 +- .../aas/handler/state/HandshakeState.kt | 3 +- .../aas/handler/state/LoginState.kt | 69 +++++++++---------- .../com/viaversion/aas/web/WebClient.kt | 4 +- .../viaversion/aas/web/WebDashboardServer.kt | 19 +++-- .../kotlin/com/viaversion/aas/web/WebLogin.kt | 13 +++- src/main/resources/web/auth.js | 31 +++++++-- src/main/resources/web/index.html | 2 +- 10 files changed, 87 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index c6f860b..656b4b3 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Example address: ```server.example.com._p25565._v1_12_2._of._uBACKUSERNAME.viaaa Address parts: - ```server.example.com```: backend server address - ```_p```: backend port -- ```_v```: backend version ([protocol id](https://wiki.vg/Protocol_version_numbers) or name with underline instead of dots). ```AUTO``` is default and 1.8 is fallback if it fails. +- ```_v```: backend version ([protocol id](https://wiki.vg/Protocol_version_numbers) or name with underline instead of dots). ```AUTO``` is default and ``-1`` is fallback if it fails. - ```_o```: ```t``` to force online mode in frontend, ```f``` to disable online mode in frontend. If not set, it will be based on backend online mode. - ```_u```: username to use in backend connection - ```viaaas.example.com```: hostname suffix (defined in config) diff --git a/build.gradle.kts b/build.gradle.kts index 2ce4bd2..1190471 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,8 @@ plugins { } application { - mainClassName = "com.viaversion.aas.VIAaaSKt" + mainClass.set("com.viaversion.aas.VIAaaSKt") + mainClassName = mainClass.get() applicationDefaultJvmArgs = listOf("-Dio.ktor.development=true") } diff --git a/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetectorHandler.kt b/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetectorHandler.kt index d99d6b9..044a9d2 100644 --- a/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetectorHandler.kt +++ b/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetectorHandler.kt @@ -25,16 +25,13 @@ class ProtocolDetectorHandler(val connectionData: ConnectionData) : ChannelDuple if (address is InetSocketAddress) { val timeoutRun = ctx.executor().schedule({ mcLogger.warn("Timeout protocol A.D. $address") - hold = false - drainQueue(ctx) ctx.pipeline().remove(this) }, 10, TimeUnit.SECONDS) - ProtocolDetector.detectVersion(address) - .whenComplete { protocol, _ -> + ProtocolDetector.detectVersion(address).whenComplete { protocol, _ -> if (protocol != null && protocol.version != -1) { connectionData.viaBackServerVer = protocol.version } else { - connectionData.viaBackServerVer = 47 // fallback + connectionData.viaBackServerVer = -1 // fallback } ctx.pipeline().remove(this) diff --git a/src/main/kotlin/com/viaversion/aas/handler/state/HandshakeState.kt b/src/main/kotlin/com/viaversion/aas/handler/state/HandshakeState.kt index 9ec42d0..a2a4ac0 100644 --- a/src/main/kotlin/com/viaversion/aas/handler/state/HandshakeState.kt +++ b/src/main/kotlin/com/viaversion/aas/handler/state/HandshakeState.kt @@ -8,6 +8,7 @@ import com.viaversion.aas.packet.Packet import com.viaversion.aas.packet.handshake.Handshake import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader +import com.google.common.net.HostAndPort import com.google.common.util.concurrent.RateLimiter import com.viaversion.aas.util.StacklessException import io.netty.channel.ChannelHandlerContext @@ -64,7 +65,7 @@ class HandshakeState : MinecraftConnectionState { (handler.data.state as? LoginState)?.also { it.frontOnline = frontOnline it.backName = parsed.username - it.backAddress = packet.address to packet.port + it.backAddress = HostAndPort.fromParts(packet.address, packet.port) } val playerAddr = handler.data.frontHandler.endRemoteAddress diff --git a/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt b/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt index 562a71c..4910a1c 100644 --- a/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt +++ b/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt @@ -1,5 +1,6 @@ package com.viaversion.aas.handler.state +import com.google.common.net.HostAndPort import com.google.gson.Gson import com.viaversion.aas.* import com.viaversion.aas.codec.CompressionCodec @@ -13,18 +14,20 @@ import io.netty.channel.Channel import io.netty.channel.ChannelHandlerContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import us.myles.ViaVersion.packets.State +import java.util.* import java.util.concurrent.CompletableFuture import javax.crypto.Cipher class LoginState : MinecraftConnectionState { - val callbackPlayerId = CompletableFuture() + val callbackPlayerId = CompletableFuture() lateinit var frontToken: ByteArray lateinit var frontServerId: String var frontOnline: Boolean? = null lateinit var frontName: String - lateinit var backAddress: Pair + lateinit var backAddress: HostAndPort var backName: String? = null var started = false override val state: State @@ -64,7 +67,6 @@ class LoginState : MinecraftConnectionState { if (threshold != -1) { pipe.addAfter("frame", "compress", CompressionCodec(threshold)) - // todo viarewind backend compression } else if (pipe.get("compress") != null) { pipe.remove("compress") } @@ -91,43 +93,34 @@ class LoginState : MinecraftConnectionState { if (frontOnline == null) { authenticateOnlineFront(handler.data.frontChannel) } + val frontHandler = handler.data.frontHandler + val backChan = handler.data.backChannel!! - callbackPlayerId.whenComplete { playerId, e -> - if (e != null) return@whenComplete - val frontHandler = handler.data.frontHandler - val backChan = handler.data.backChannel!! + GlobalScope.launch(Dispatchers.IO) { + try { + val playerId = callbackPlayerId.await() - GlobalScope.launch(Dispatchers.IO) { - try { - val backKey = generate128Bits() - val backHash = generateServerHash(backServerId, backKey, backPublicKey) + val backKey = generate128Bits() + val backHash = generateServerHash(backServerId, backKey, backPublicKey) - viaWebServer.requestSessionJoin( - parseUndashedId(playerId), - backName!!, - backHash, - frontHandler.endRemoteAddress, - handler.data.backHandler!!.endRemoteAddress - ).whenCompleteAsync({ _, throwable -> - if (throwable != null) { - frontHandler.data.frontChannel.pipeline() - .fireExceptionCaught(throwable) - return@whenCompleteAsync - } + viaWebServer.requestSessionJoin( + playerId, + backName!!, + backHash, + frontHandler.endRemoteAddress, + handler.data.backHandler!!.endRemoteAddress + ).await() - val cryptoResponse = CryptoResponse() - cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey) - cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken) - forward(frontHandler, cryptoResponse, true) + val cryptoResponse = CryptoResponse() + cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey) + cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken) + val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE) + val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE) - val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE) - val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE) - backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(backAesDe, backAesEn)) - }, backChan.eventLoop()) - } catch (e: Exception) { - frontHandler.data.frontChannel.pipeline() - .fireExceptionCaught(e) - } + forward(frontHandler, cryptoResponse, true) + backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(backAesDe, backAesEn)) + } catch (e: Exception) { + frontHandler.data.frontChannel.pipeline().fireExceptionCaught(e) } } } @@ -152,7 +145,7 @@ class LoginState : MinecraftConnectionState { val profile = hasJoined(frontName, frontHash) val id = profile.get("id")!!.asString - callbackPlayerId.complete(id) + callbackPlayerId.complete(parseUndashedId(id)) } catch (e: Exception) { callbackPlayerId.completeExceptionally(e) } @@ -169,7 +162,7 @@ class LoginState : MinecraftConnectionState { backName = backName ?: frontName val connect = { - connectBack(handler, backAddress.first, backAddress.second, State.LOGIN) { + connectBack(handler, backAddress.host, backAddress.port, State.LOGIN) { loginStart.username = backName!! send(handler.data.backChannel!!, loginStart, true) } @@ -187,7 +180,7 @@ class LoginState : MinecraftConnectionState { } when (frontOnline) { - false -> callbackPlayerId.complete(generateOfflinePlayerUuid(frontName).toString().replace("-", "")) + false -> callbackPlayerId.complete(generateOfflinePlayerUuid(frontName)) true -> authenticateOnlineFront(handler.data.frontChannel) // forced null -> connect() // Connect then authenticate } diff --git a/src/main/kotlin/com/viaversion/aas/web/WebClient.kt b/src/main/kotlin/com/viaversion/aas/web/WebClient.kt index 4df62d3..4ddfee4 100644 --- a/src/main/kotlin/com/viaversion/aas/web/WebClient.kt +++ b/src/main/kotlin/com/viaversion/aas/web/WebClient.kt @@ -28,8 +28,8 @@ data class WebClient( return server.listeners.put(uuid, this) } - fun unlistenId(uuid: UUID) { + fun unlistenId(uuid: UUID): Boolean { server.listeners.remove(uuid, this) - listenedIds.remove(uuid) + return listenedIds.remove(uuid) } } diff --git a/src/main/kotlin/com/viaversion/aas/web/WebDashboardServer.kt b/src/main/kotlin/com/viaversion/aas/web/WebDashboardServer.kt index 2055ff3..7fc313e 100644 --- a/src/main/kotlin/com/viaversion/aas/web/WebDashboardServer.kt +++ b/src/main/kotlin/com/viaversion/aas/web/WebDashboardServer.kt @@ -18,6 +18,7 @@ import io.ktor.client.request.* import io.ktor.http.cio.websocket.* import io.ktor.websocket.* import kotlinx.coroutines.* +import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.time.delay import java.net.InetSocketAddress import java.net.SocketAddress @@ -45,7 +46,7 @@ class WebDashboardServer { } fun checkToken(token: String): UUID? { - try { + return try { val verified = JWT.require(jwtAlgorithm) .withSubject("mc_account") .withClaim("can_listen", true) @@ -53,9 +54,9 @@ class WebDashboardServer { .build() .verify(token) - return UUID.fromString(verified.getClaim("mc_uuid").asString()) + UUID.fromString(verified.getClaim("mc_uuid").asString()) } catch (e: JWTVerificationException) { - return null + null } } @@ -68,13 +69,11 @@ class WebDashboardServer { ) val usernameIdCache = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) - .build(CacheLoader.from { name -> - runBlocking { - withContext(Dispatchers.IO) { - httpClient.get("https://api.mojang.com/users/profiles/minecraft/$name") - ?.get("id")?.asString?.let { parseUndashedId(it) } - } - } + .build>(CacheLoader.from { name -> + GlobalScope.async(Dispatchers.IO) { + httpClient.get("https://api.mojang.com/users/profiles/minecraft/$name") + ?.get("id")?.asString?.let { parseUndashedId(it) } + }.asCompletableFuture() }) val sessionHashCallbacks = CacheBuilder.newBuilder() diff --git a/src/main/kotlin/com/viaversion/aas/web/WebLogin.kt b/src/main/kotlin/com/viaversion/aas/web/WebLogin.kt index 2c10d37..a625244 100644 --- a/src/main/kotlin/com/viaversion/aas/web/WebLogin.kt +++ b/src/main/kotlin/com/viaversion/aas/web/WebLogin.kt @@ -10,6 +10,7 @@ import com.viaversion.aas.webLogger import io.ktor.client.request.forms.* import io.ktor.http.* import io.ktor.http.cio.websocket.* +import kotlinx.coroutines.future.await import java.net.URLEncoder import java.util.* @@ -24,7 +25,6 @@ class WebLogin : WebState { when (obj.getAsJsonPrimitive("action").asString) { "offline_login" -> { - // todo add some spam check val username = obj.get("username").asString.trim() val uuid = generateOfflinePlayerUuid(username) @@ -51,7 +51,8 @@ class WebLogin : WebState { if (check.getAsJsonPrimitive("valid").asBoolean) { val mcIdUser = check.get("username").asString val uuid = check.get("uuid")?.asString?.let { parseUndashedId(it.replace("-", "")) } - ?: webClient.server.usernameIdCache.get(mcIdUser) + ?: webClient.server.usernameIdCache.get(mcIdUser).await() + ?: throw StacklessException("Failed to get UUID from minecraft.id") val token = webClient.server.generateToken(uuid) webClient.ws.send(JsonObject().also { @@ -90,7 +91,13 @@ class WebLogin : WebState { } "unlisten_login_requests" -> { val uuid = UUID.fromString(obj.getAsJsonPrimitive("uuid").asString) - webClient.unlistenId(uuid) + webLogger.info("Unlisten: ${webClient.id}: $uuid") + val response = JsonObject().also { + it.addProperty("action", "unlisten_login_requests_result") + it.addProperty("uuid", uuid.toString()) + it.addProperty("success", webClient.unlistenId(uuid)) + } + webClient.ws.send(response.toString()) } "session_hash_response" -> { val hash = obj.get("session_hash").asString diff --git a/src/main/resources/web/auth.js b/src/main/resources/web/auth.js index 9bdf963..f84e30b 100644 --- a/src/main/resources/web/auth.js +++ b/src/main/resources/web/auth.js @@ -8,9 +8,13 @@ let urlParams = new URLSearchParams(); window.location.hash.substr(1).split("?").map(it => new URLSearchParams(it).forEach((a, b) => urlParams.append(b, a))); var mcIdUsername = urlParams.get("username"); var mcauth_code = urlParams.get("mcauth_code"); -if (urlParams.get("mcauth_success") == "false") { +var mcauth_success = urlParams.get("mcauth_success"); +if (mcauth_success == "false") { addToast("Couldn't authenticate with Minecraft.ID", urlParams.get("mcauth_msg")); } +if (mcauth_code != null) { + history.replaceState(null, null, "#"); +} // WS url function defaultWs() { @@ -271,9 +275,25 @@ function addAction(text, onClick) { actions.appendChild(p); } function addListeningList(user) { - let msg = document.createElement("p"); - msg.innerText = "Listening to login: " + user; - listening.appendChild(msg); + let p = document.createElement("p"); + let head = document.createElement("img"); + let n = document.createElement("span"); + let remove = document.createElement("a"); + n.innerText = " " + user + " "; + remove.innerText = "Unlisten"; + remove.href = "javascript:"; + remove.onclick = () => { + // todo remove the token + listening.removeChild(p); + unlisten(user); + }; + head.className = "account_head"; + head.alt = user + "'s head"; + head.src = "https://crafthead.net/helm/" + user; + p.append(head); + p.append(n); + p.append(remove); + listening.appendChild(p); } function addToast(title, msg) { let toast = document.createElement("div"); @@ -345,6 +365,9 @@ new BroadcastChannel("viaaas-notification").addEventListener("message", handleSW function listen(token) { socket.send(JSON.stringify({"action": "listen_login_requests", "token": token})); } +function unlisten(id) { + socket.send(JSON.stringify({"action": "unlisten_login_requests", "uuid": id})); +} function confirmJoin(hash) { socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash})); } diff --git a/src/main/resources/web/index.html b/src/main/resources/web/index.html index cbcc570..34d6a8a 100644 --- a/src/main/resources/web/index.html +++ b/src/main/resources/web/index.html @@ -63,7 +63,7 @@

CORS Proxy status: ?


-

+

Listening to logins from: