address in login page, update via

implements #194
This commit is contained in:
creeper123123321 2022-03-19 15:27:01 -03:00
parent e80c1384c2
commit 49a39eb592
13 changed files with 210 additions and 72 deletions

View File

@ -57,8 +57,8 @@ dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
val vvVer = "4.2.2-SNAPSHOT"
val vbVer = "4.2.1-SNAPSHOT"
val vvVer = "4.3.0-22w11a-SNAPSHOT"
val vbVer = "4.3.0-22w11a-SNAPSHOT"
val vrVer = "ae62eba"
implementation("com.viaversion:viaversion:$vvVer") { isTransitive = false }
implementation("com.viaversion:viabackwards:$vbVer") { isTransitive = false }
@ -68,7 +68,7 @@ dependencies {
implementation("io.netty:netty-tcnative-boringssl-static:2.0.50.Final")
implementation("io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.12.Final:linux-x86_64")
implementation("com.google.guava:guava:31.0.1-jre")
implementation("com.google.guava:guava:31.1-jre")
implementation("com.velocitypowered:velocity-native:3.1.0")
implementation("net.coobird:thumbnailator:0.4.17")
implementation("org.powernukkit.fastutil:fastutil-lite:8.1.1")

View File

@ -41,4 +41,9 @@ public class StatusKicked implements ConnectionState {
public void onInactivated(@NotNull MinecraftHandler handler) {
ConnectionState.DefaultImpls.onInactivated(this, handler);
}
@Override
public void start(@NotNull MinecraftHandler handler) {
ConnectionState.DefaultImpls.start(this, handler);
}
}

View File

@ -5,9 +5,7 @@ import com.google.common.primitives.Ints;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import kotlin.text.StringsKt;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
import java.util.regex.Pattern;
public class AddressParser {
@ -19,11 +17,18 @@ public class AddressParser {
public String username;
public Boolean online;
public AddressParser parse(String address, String viaHostName) {
address = StringsKt.removeSuffix(address, ".");
String suffixRemoved = StringsKt.removeSuffix(address, "." + viaHostName);
public AddressParser parse(String rawAddress, List<String> viaHostName) {
String address = StringsKt.removeSuffix(rawAddress, ".");
if (suffixRemoved.equals(address)) {
String suffix = viaHostName.stream()
.filter(s -> StringsKt.endsWith("." + address, s, true))
.findAny()
.orElse(null);
String suffixRemoved;
if (suffix != null) {
suffixRemoved = StringsKt.removeSuffix(address, "." + suffix);
} else {
serverAddress = address;
return this;
}
@ -43,7 +48,7 @@ public class AddressParser {
serverAddress = String.join(".", Lists.reverse(serverParts));
viaOptions = String.join(".", Lists.reverse(optionsParts));
viaSuffix = viaHostName;
viaSuffix = suffix;
return this;
}
@ -61,7 +66,7 @@ public class AddressParser {
}
String arg = part.substring(2);
switch (option) {
switch (option.toLowerCase(Locale.ROOT)) {
case "o": {
parseOnlineMode(arg);
break;

View File

@ -32,4 +32,7 @@ interface ConnectionState {
logDisconnect(handler, "-")
handler.other?.close()
}
fun start(handler: MinecraftHandler) {
}
}

View File

@ -7,16 +7,12 @@ import com.google.common.util.concurrent.RateLimiter
import com.viaversion.aas.codec.packet.Packet
import com.viaversion.aas.codec.packet.handshake.Handshake
import com.viaversion.aas.config.VIAaaSConfig
import com.viaversion.aas.fireExceptionCaughtIfOpen
import com.viaversion.aas.handler.MinecraftHandler
import com.viaversion.aas.mcLogger
import com.viaversion.aas.setAutoRead
import com.viaversion.aas.util.AddressParser
import com.viaversion.aas.util.StacklessException
import com.viaversion.viaversion.api.protocol.packet.State
import io.netty.channel.ChannelHandlerContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.InetAddress
import java.net.InetSocketAddress
import java.util.concurrent.TimeUnit
@ -52,7 +48,7 @@ class HandshakeState : ConnectionState {
private fun handleNextState(handler: MinecraftHandler, packet: Handshake) {
handler.data.frontVer = packet.protocolId
when (packet.nextState.ordinal) {
1 -> handler.data.state = StatusState
1 -> handler.data.state = StatusState()
2 -> handler.data.state = LoginState()
else -> throw StacklessException("Invalid next state")
}
@ -63,36 +59,42 @@ class HandshakeState : ConnectionState {
val extraData = packet.address.substringAfter(0.toChar(), missingDelimiterValue = "").ifEmpty { null }
val virtualHostNoExtra = packet.address.substringBefore(0.toChar())
val parsed = VIAaaSConfig.hostName.map {
AddressParser().parse(virtualHostNoExtra, it)
}.sortedBy {
it.viaSuffix == null
}.first()
val parsed = AddressParser().parse(virtualHostNoExtra, VIAaaSConfig.hostName)
val backProto = parsed.protocol ?: -2
val hadHostname = parsed.viaSuffix != null
packet.address = parsed.serverAddress!!
packet.port = parsed.port ?: VIAaaSConfig.defaultBackendPort ?: virtualPort
val backAddress = parsed.serverAddress!!
val port = parsed.port ?: VIAaaSConfig.defaultBackendPort ?: virtualPort
val host = HostAndPort.fromParts(backAddress, port)
var frontOnline = parsed.online
if (VIAaaSConfig.forceOnlineMode) frontOnline = true
val addressFromWeb = VIAaaSConfig.hostName.any { parsed.serverAddress.equals(it, ignoreCase = true) }
handler.data.backServerVer = backProto
(handler.data.state as? LoginState)?.also {
it.frontOnline = frontOnline
it.backName = parsed.username
it.backAddress = HostAndPort.fromParts(packet.address, packet.port)
if (!addressFromWeb) {
it.backAddress = host
}
it.extraData = extraData
}
(handler.data.state as? StatusState)?.also {
if (!addressFromWeb) {
it.address = host
}
}
val playerAddr = handler.data.frontHandler.endRemoteAddress
mcLogger.debug(
"HS: $playerAddr ${handler.data.state.state.toString().substring(0, 1)} " +
"HS: $playerAddr ${handler.data.state.state.name[0]} " +
"$virtualHostNoExtra $virtualPort v${handler.data.frontVer}"
)
if (!hadHostname && VIAaaSConfig.requireHostName) {
if (!hadHostname && VIAaaSConfig.requireHostName && !addressFromWeb
) {
throw StacklessException("Missing parts in hostname")
}
}
@ -104,20 +106,7 @@ class HandshakeState : ConnectionState {
checkRateLimit(handler, packet.nextState)
handleVirtualHost(handler, packet)
connectStatus(handler, ctx, packet)
}
private fun connectStatus(handler: MinecraftHandler, ctx: ChannelHandlerContext, packet: Handshake) {
if (packet.nextState == State.STATUS) { // see LoginState for LOGIN
handler.data.frontChannel.setAutoRead(false)
handler.coroutineScope.launch(Dispatchers.IO) {
try {
connectBack(handler, packet.address, packet.port, packet.nextState)
} catch (e: Exception) {
ctx.channel().fireExceptionCaughtIfOpen(e)
}
}
}
handler.data.state.start(handler)
}
override fun disconnect(handler: MinecraftHandler, msg: String) {

View File

@ -32,7 +32,7 @@ class LoginState : ConnectionState {
lateinit var frontServerId: String
var frontOnline: Boolean? = null
lateinit var frontName: String
lateinit var backAddress: HostAndPort
var backAddress: HostAndPort? = null
lateinit var cryptoKey: KeyPair
var extraData: String? = null
var backName: String? = null
@ -226,15 +226,24 @@ class LoginState : ConnectionState {
handler.coroutineScope.launch(Dispatchers.IO) {
try {
if (backAddress == null) {
mcLogger.info("Requesting address info from web for $frontName")
val info = AspirinServer.viaWebServer.requestAddressInfo(frontName).await()
backAddress = info.backHostAndPort
handler.data.backServerVer = info.backVersion
frontOnline = info.frontOnline
}
if (VIAaaSConfig.forceOnlineMode) frontOnline = true
if (frontOnline != null) {
when (frontOnline) {
false -> callbackPlayerId.complete(generateOfflinePlayerUuid(frontName))
true -> authenticateOnlineFront(handler.data.frontChannel) // forced
else -> {}
}
val id = callbackPlayerId.await()
mcLogger.info("Login: ${handler.endRemoteAddress} $frontName $id")
}
connectBack(handler, backAddress.host, backAddress.port, State.LOGIN, extraData)
connectBack(handler, HostAndPort.fromParts(backAddress!!.host, backAddress!!.port), State.LOGIN, extraData)
loginStart.username = backName!!
send(handler.data.backChannel!!, loginStart, true)
} catch (e: Exception) {

View File

@ -1,25 +1,27 @@
package com.viaversion.aas.handler.state
import com.google.common.net.HostAndPort
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.viaversion.aas.AspirinServer
import com.viaversion.aas.*
import com.viaversion.aas.codec.packet.Packet
import com.viaversion.aas.codec.packet.UnknownPacket
import com.viaversion.aas.codec.packet.status.StatusResponse
import com.viaversion.aas.config.VIAaaSConfig
import com.viaversion.aas.handler.MinecraftHandler
import com.viaversion.aas.handler.forward
import com.viaversion.aas.parseProtocol
import com.viaversion.aas.send
import com.viaversion.aas.util.StacklessException
import com.viaversion.viaversion.api.protocol.packet.State
import io.netty.channel.ChannelHandlerContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
object StatusState : ConnectionState {
class StatusState : ConnectionState {
override val state: State
get() = State.STATUS
var address: HostAndPort? = null
override fun handlePacket(handler: MinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
if (packet is UnknownPacket) throw StacklessException("Invalid packet")
@ -72,4 +74,19 @@ object StatusState : ConnectionState {
send(handler.data.frontChannel, packet, flush = true)
handler.data.state = StatusKicked()
}
override fun start(handler: MinecraftHandler) {
handler.data.frontChannel.setAutoRead(false)
handler.coroutineScope.launch(Dispatchers.IO) {
try {
if (address != null) {
connectBack(handler, address!!, state)
} else {
handler.disconnect("VIAaaS")
}
} catch (e: Exception) {
handler.data.frontChannel.fireExceptionCaughtIfOpen(e)
}
}
}
}

View File

@ -51,7 +51,7 @@ private suspend fun createBackChannel(
.channel()
(channel.pipeline()["proxy"] as? ProxyHandler)?.connectFuture()?.suspendAwait()
mcLogger.info("+ ${state.name.substring(0, 1)} ${handler.endRemoteAddress} -> $socketAddr")
mcLogger.info("+ ${state.name[0]} ${handler.endRemoteAddress} -> $socketAddr")
handler.data.backChannel = channel as SocketChannel
autoDetectVersion(handler, socketAddr)
@ -143,12 +143,11 @@ private suspend fun resolveBackendAddresses(hostAndPort: HostAndPort): List<Inet
suspend fun connectBack(
handler: MinecraftHandler,
address: String,
port: Int,
address: HostAndPort,
state: State,
extraData: String? = null
) {
val addresses = resolveBackendAddresses(HostAndPort.fromParts(address, port))
val addresses = resolveBackendAddresses(address)
if (addresses.isEmpty()) throw StacklessException("Hostname has no IP addresses")

View File

@ -1,9 +1,12 @@
package com.viaversion.aas.web
import com.google.common.net.HostAndPort
import com.google.common.primitives.Ints
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.viaversion.aas.*
import com.viaversion.aas.util.StacklessException
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion
import io.ktor.client.call.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
@ -27,10 +30,11 @@ class WebLogin : WebState {
when (obj.getAsJsonPrimitive("action").asString) {
"offline_login" -> handleOfflineLogin(webClient, msg, obj)
"minecraft_id_login" -> handleMcIdLogin(webClient, msg, obj)
"listen_login_requests" -> handleListenLogins(webClient, msg, obj)
"unlisten_login_requests" -> handleUnlisten(webClient, msg, obj)
"session_hash_response" -> handleSessionResponse(webClient, msg, obj)
"minecraft_id_login" -> handleMcIdLogin(webClient, obj)
"listen_login_requests" -> handleListenLogins(webClient, obj)
"unlisten_login_requests" -> handleUnlisten(webClient, obj)
"session_hash_response" -> handleSessionResponse(webClient, obj)
"parameters_response" -> handleParametersResponse(webClient, obj)
else -> throw StacklessException("invalid action!")
}
@ -77,7 +81,7 @@ class WebLogin : WebState {
webLogger.info("Token gen: ${webClient.id}: offline $username $uuid")
}
private suspend fun handleMcIdLogin(webClient: WebClient, msg: String, obj: JsonObject) {
private suspend fun handleMcIdLogin(webClient: WebClient, obj: JsonObject) {
val username = obj["username"].asString
val code = obj["code"].asString
@ -102,7 +106,7 @@ class WebLogin : WebState {
}
}
private suspend fun handleListenLogins(webClient: WebClient, msg: String, obj: JsonObject) {
private suspend fun handleListenLogins(webClient: WebClient, obj: JsonObject) {
val token = obj.getAsJsonPrimitive("token").asString
val user = webClient.server.parseToken(token)
val response = JsonObject().also {
@ -121,7 +125,7 @@ class WebLogin : WebState {
webClient.ws.send(response.toString())
}
private suspend fun handleUnlisten(webClient: WebClient, msg: String, obj: JsonObject) {
private suspend fun handleUnlisten(webClient: WebClient, obj: JsonObject) {
val uuid = UUID.fromString(obj.getAsJsonPrimitive("uuid").asString)
webLogger.info("Unlisten: ${webClient.id}: $uuid")
val response = JsonObject().also {
@ -132,8 +136,26 @@ class WebLogin : WebState {
webClient.ws.send(response.toString())
}
private suspend fun handleSessionResponse(webClient: WebClient, msg: String, obj: JsonObject) {
private fun handleSessionResponse(webClient: WebClient, obj: JsonObject) {
val hash = obj["session_hash"].asString
webClient.server.sessionHashCallbacks.getIfPresent(hash)?.complete(Unit)
}
private fun handleParametersResponse(webClient: WebClient, obj: JsonObject) {
val callback = UUID.fromString(obj["callback"].asString)
webClient.server.addressCallbacks[callback].complete(
WebServer.AddressInfo(
backVersion = obj["version"].asString.let {
var protocol = Ints.tryParse(it)
if (protocol == null) {
val ver = ProtocolVersion.getClosest(it)
if (ver != null) protocol = ver.version
}
protocol ?: -2
},
backHostAndPort = HostAndPort.fromParts(obj["host"].asString, obj["port"].asInt),
frontOnline = obj["frontOnline"].asString.toBooleanStrictOrNull()
)
)
}
}

View File

@ -7,13 +7,11 @@ import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.collect.MultimapBuilder
import com.google.common.collect.Multimaps
import com.google.common.net.HostAndPort
import com.google.gson.JsonObject
import com.viaversion.aas.AspirinServer
import com.viaversion.aas.*
import com.viaversion.aas.config.VIAaaSConfig
import com.viaversion.aas.parseUndashedId
import com.viaversion.aas.reverseLookup
import com.viaversion.aas.util.StacklessException
import com.viaversion.aas.webLogger
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.server.netty.*
@ -25,6 +23,7 @@ import io.netty.handler.codec.dns.DnsRecordType
import io.netty.util.ReferenceCountUtil
import kotlinx.coroutines.*
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await
import java.net.InetSocketAddress
import java.net.SocketAddress
import java.time.Duration
@ -78,14 +77,63 @@ class WebServer {
.build<String, CompletableFuture<UUID?>>(CacheLoader.from { name ->
CoroutineScope(Dispatchers.IO).async {
AspirinServer.httpClient
.get("https://api.mojang.com/users/profiles/minecraft/$name").body<JsonObject?>()
?.get("id")?.asString?.let { parseUndashedId(it) }
.get("https://api.mojang.com/users/profiles/minecraft/$name")
.body<JsonObject?>()?.get("id")?.asString?.let { parseUndashedId(it) }
}.asCompletableFuture()
})
val sessionHashCallbacks = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.build<String, CompletableFuture<Unit>>(CacheLoader.from { _ -> CompletableFuture() })
val addressCallbacks = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.build<UUID, CompletableFuture<AddressInfo>>(CacheLoader.from { _ -> CompletableFuture() })
data class AddressInfo(val backVersion: Int, val backHostAndPort: HostAndPort, var frontOnline: Boolean? = null)
suspend fun requestAddressInfo(frontName: String): CompletableFuture<AddressInfo> {
var onlineId: UUID? = null
try {
onlineId = usernameIdCache[frontName].await()
} catch (e: java.lang.Exception) {
webLogger.debug("Couldn't get online uuid for $frontName", e)
}
val offlineId = generateOfflinePlayerUuid(frontName)
val callbackId = UUID.randomUUID()
val future = addressCallbacks.get(callbackId)
CoroutineScope(coroutineContext).apply {
launch(Dispatchers.IO) {
run sending@{
onlineId?.let {
if (sendRequestAddress(it, callbackId)) return@sending
}
if (sendRequestAddress(offlineId, callbackId)) return@sending
future.completeExceptionally(StacklessException("Username $frontName not listened. Use web auth to select backend server address."))
}
}
launch {
delay(20_000)
future.completeExceptionally(StacklessException("No response from browser"))
}
}
return future
}
private suspend fun sendRequestAddress(uuid: UUID, callbackId: UUID): Boolean {
var sent = false
listeners.get(uuid).forEach {
it.ws.send(
JsonObject().also {
it.addProperty("action", "parameters_request")
it.addProperty("callback", callbackId.toString())
}.toString()
)
it.ws.flush()
sent = true
}
return sent
}
suspend fun requestSessionJoin(
frontName: String,

View File

@ -91,12 +91,32 @@ script-src 'self' https://*.cloudflare.com/ https://alcdn.msauth.net/ https://*.
<p>WebSocket connection status: <span class="text-white bg-dark" id="connection_status">?</span></p>
<p>CORS Proxy status: <span class="text-white bg-dark" id="cors_status">?</span></p>
<hr>
<p>Listening to frontend logins from: <span id="listening"></span></p>
<p>Listening to frontend logins from:</p>
<div id="listening"></div>
<div id="actions">
<button id="listen_continue" type="button" class="btn btn-primary">Listen to</button>
<button id="listen_continue" type="button" class="btn btn-primary">Listen to <span id="mcIdUsername"></span>
</button>
<button id="listen_premium" type="button" class="btn btn-primary">Listen to premium login</button>
<button id="listen_offline" type="button" class="btn btn-primary">Listen to offline mode login</button>
</div>
<hr>
<p>Connecting to backend server:</p>
<div id="connect">
<form class="input-group">
<input id="connect_address" type="text" class="form-control" placeholder="Address">
<input id="connect_port" type="number" min="1" max="65535" class="form-control"
placeholder="Port">
<input id="connect_version" type="text" class="form-control" placeholder="Version">
<select class="form-select" id="connect_online">
<option value="null" selected>Front Online Mode...</option>
<option>AUTO</option>
<option value="true">Force online</option>
<option value="false">Force offline (recommended for Geyser)</option>
</select>
</form>
</div>
<p>You can also use <a href="https://jo0001.github.io/ViaSetup/aspirin" id="viasetup_address">address
generator</a></p>
</div>
<div aria-labelledby="settings-tab" class="tab-pane fade" id="settings">

View File

@ -1,6 +1,6 @@
// Minecraft.id
let urlParams = new URLSearchParams();
window.location.hash.substr(1).split("?")
window.location.hash.substring(1).split("?")
.map(it => new URLSearchParams(it)
.forEach((a, b) => urlParams.append(b, a)));
let mcIdUsername = urlParams.get("username");
@ -138,7 +138,8 @@ $("#listen_offline").on("click", () => {
workers.forEach(it => it.postMessage({action: "listen_pow", user: user, id: taskId, deltaTime: deltaTime}));
addToast("Offline username", "Please wait a minute...");
});
$("#listen_continue").append(document.createTextNode(" " + mcIdUsername)).on("click", () => {
$("#mcIdUsername").text(mcIdUsername);
$("#listen_continue").on("click", () => {
sendSocket(JSON.stringify({
"action": "minecraft_id_login",
"username": mcIdUsername,
@ -449,7 +450,7 @@ class MojangAccount extends McAccount {
headers: {"content-type": "application/json"},
})
.then(r => {
if (r.status == 403) {
if (r.status === 403) {
this.logout();
throw "403, token expired?";
}
@ -548,6 +549,13 @@ class MicrosoftAccount extends McAccount {
})
);
}
checkActive() {
return fetch(getCorsProxy() + "https://api.minecraftservices.com/entitlements/mcstore", {
method: "get",
headers: {"authorization": "Bearer " + this.accessToken}
}).then(data => data.ok);
}
}
function findAccountByMcName(name) {
@ -727,9 +735,22 @@ function onSocketMsg(event) {
}
} else if (parsed.action === "session_hash_request") {
handleJoinRequest(parsed);
} else if (parsed.action === "parameters_request") {
handleParametersRequest(parsed);
}
}
function handleParametersRequest(parsed) {
socket.send(JSON.stringify({
action: "parameters_response",
callback: parsed["callback"],
version: $("#connect_version").val(),
host: $("#connect_address").val(),
port: parseInt($("#connect_port").val()) || 25565,
frontOnline: $("#connect_online").val()
}));
}
function listenStoredTokens() {
getTokens().forEach(listen);
}

View File

@ -17,7 +17,7 @@ self.addEventListener("fetch", evt => {
if (!shouldCache(evt.request.url)
|| evt.request.method !== "GET") return;
evt.respondWith(
fromCache(evt.request).catch(() => fromNetwork(evt.request))
fromNetwork(evt.request).catch(() => fromCache(evt.request))
);
});