mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-09-28 04:37:40 +02:00
some ugly css, allow continuing without auth, code refactors, add gradle versions plugin, fix null address, add snakeyaml, coroutines, override viaconnectionmanager
This commit is contained in:
parent
f57ec67803
commit
dacd630d73
@ -13,11 +13,11 @@ Idea: server.example.com._p25565._v1_12_2._otrue.viaaas.example.com (default bac
|
||||
- VIAaaS may have security vulnerabilities, make sure to block the ports in firewall and take care of browser local storage.
|
||||
|
||||
Usage for offline mode:
|
||||
- ./gradlew clean run
|
||||
- Run the shadow jar or ./gradlew clean run
|
||||
- Connect to mc.example.com._v1_8.viaaas.localhost
|
||||
|
||||
Usage for online mode (may block your Mojang account):
|
||||
- ./gradlew clean run
|
||||
- Run the shadow jar or ./gradlew clean run
|
||||
- You'll need 2 premium accounts for online mode
|
||||
- Set up a CORS Proxy (something like https://github.com/Rob--W/cors-anywhere (less likely to look suspicious to Mojang if you run on your local machine) or https://github.com/Zibri/cloudflare-cors-anywhere (more suspicious)).
|
||||
- Go to https://localhost:25543/auth.html, configure the CORS Proxy URL and listen to the username you're using to connect.
|
||||
|
@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id("com.github.johnrengelman.shadow") version "6.1.0"
|
||||
id("com.github.ben-manes.versions") version "0.34.0"
|
||||
application
|
||||
kotlin("jvm") version "1.4.10"
|
||||
}
|
||||
@ -28,6 +29,7 @@ dependencies {
|
||||
implementation("nl.matsv:viabackwards-all:3.2.0")
|
||||
implementation("de.gerrygames:viarewind-all:1.5.2")
|
||||
implementation("io.netty:netty-all:4.1.53.Final")
|
||||
implementation("org.yaml:snakeyaml:1.26")
|
||||
|
||||
implementation("org.apache.logging.log4j:log4j-core:2.13.3")
|
||||
implementation("org.apache.logging.log4j:log4j-slf4j-impl:2.13.3")
|
||||
@ -59,5 +61,6 @@ tasks {
|
||||
}
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
dependsOn(named("dependencyUpdates"))
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import com.google.common.net.UrlEscapers
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.cio.websocket.*
|
||||
import io.netty.bootstrap.Bootstrap
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.ByteBufAllocator
|
||||
@ -18,14 +17,15 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import us.myles.ViaVersion.api.Via
|
||||
import us.myles.ViaVersion.api.data.StoredObject
|
||||
import us.myles.ViaVersion.api.data.UserConnection
|
||||
import us.myles.ViaVersion.api.type.Type
|
||||
import us.myles.ViaVersion.exception.CancelCodecException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.SocketAddress
|
||||
import java.security.KeyFactory
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.util.*
|
||||
@ -54,6 +54,7 @@ class CloudMinecraftHandler(val user: UserConnection,
|
||||
var other: Channel?,
|
||||
val frontEnd: Boolean) : SimpleChannelInboundHandler<ByteBuf>() {
|
||||
val data get() = user.get(HandlerData::class.java)
|
||||
var address: SocketAddress? = null
|
||||
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
if (!user.isPendingDisconnect) {
|
||||
@ -63,14 +64,15 @@ class CloudMinecraftHandler(val user: UserConnection,
|
||||
}
|
||||
}
|
||||
|
||||
override fun channelActive(ctx: ChannelHandlerContext?) {
|
||||
override fun channelActive(ctx: ChannelHandlerContext) {
|
||||
address = ctx.channel().remoteAddress()
|
||||
if (data == null) {
|
||||
user.put(HandlerData(user, HandshakeState()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun channelInactive(ctx: ChannelHandlerContext) {
|
||||
chLogger.info(ctx.channel().remoteAddress().toString() + " was disconnected")
|
||||
chLogger.info(address?.toString() + " was disconnected")
|
||||
other?.close()
|
||||
}
|
||||
|
||||
@ -91,7 +93,7 @@ class CloudMinecraftHandler(val user: UserConnection,
|
||||
fun disconnect(s: String) {
|
||||
if (user.channel?.isActive != true) return
|
||||
|
||||
chLogger.info("Disconnecting " + user.channel!!.remoteAddress() + ": " + s)
|
||||
chLogger.info("Disconnecting $address: $s")
|
||||
data!!.state.disconnect(this, s)
|
||||
}
|
||||
}
|
||||
@ -117,8 +119,8 @@ class HandshakeState : MinecraftConnectionState {
|
||||
}
|
||||
|
||||
handler.user.channel!!.setAutoRead(false)
|
||||
Via.getPlatform().runAsync {
|
||||
val frontForwarder = handler.user.channel!!.pipeline().get(CloudMinecraftHandler::class.java)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val frontHandler = handler.user.channel!!.pipeline().get(CloudMinecraftHandler::class.java)
|
||||
try {
|
||||
var srvResolvedAddr = backAddr
|
||||
var srvResolvedPort = backPort
|
||||
@ -150,11 +152,11 @@ class HandshakeState : MinecraftConnectionState {
|
||||
|
||||
bootstrap.addListener {
|
||||
if (it.isSuccess) {
|
||||
CloudHeadProtocol.logger.info("conected ${handler.user.channel?.remoteAddress()} to $socketAddr")
|
||||
CloudHeadProtocol.logger.info("Connected ${frontHandler} to $socketAddr")
|
||||
|
||||
val sockChan = bootstrap.channel() as SocketChannel
|
||||
sockChan.pipeline().get(CloudMinecraftHandler::class.java).other = handler.user.channel
|
||||
frontForwarder.other = sockChan
|
||||
val backChan = bootstrap.channel() as SocketChannel
|
||||
backChan.pipeline().get(CloudMinecraftHandler::class.java).other = handler.user.channel
|
||||
frontHandler.other = backChan
|
||||
|
||||
val backHandshake = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
@ -163,7 +165,7 @@ class HandshakeState : MinecraftConnectionState {
|
||||
Type.STRING.write(backHandshake, srvResolvedAddr) // Server Address
|
||||
backHandshake.writeShort(srvResolvedPort)
|
||||
Type.VAR_INT.writePrimitive(backHandshake, nextAddr)
|
||||
sockChan.writeAndFlush(backHandshake.retain())
|
||||
backChan.writeAndFlush(backHandshake.retain())
|
||||
} finally {
|
||||
backHandshake.release()
|
||||
}
|
||||
@ -171,13 +173,13 @@ class HandshakeState : MinecraftConnectionState {
|
||||
handler.user.channel!!.setAutoRead(true)
|
||||
} else {
|
||||
handler.user.channel!!.eventLoop().submit {
|
||||
frontForwarder.disconnect("Couldn't connect: " + it.cause().toString())
|
||||
frontHandler.disconnect("Couldn't connect: " + it.cause().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
handler.user.channel!!.eventLoop().submit {
|
||||
frontForwarder.disconnect("Couldn't connect: $e")
|
||||
frontHandler.disconnect("Couldn't connect: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -263,28 +265,14 @@ class LoginState : MinecraftConnectionState {
|
||||
|
||||
fun handleCryptoResponse(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
||||
val frontHash = let {
|
||||
val frontKey = Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.DECRYPT_MODE, mcCryptoKey.private)
|
||||
it.doFinal(Type.BYTE_ARRAY_PRIMITIVE.read(msg))
|
||||
}
|
||||
val frontKey = decryptRsa(mcCryptoKey.private, Type.BYTE_ARRAY_PRIMITIVE.read(msg))
|
||||
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
|
||||
val decryptedToken = Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.DECRYPT_MODE, mcCryptoKey.private)
|
||||
it.doFinal(Type.BYTE_ARRAY_PRIMITIVE.read(msg))
|
||||
}
|
||||
val decryptedToken = decryptRsa(mcCryptoKey.private, Type.BYTE_ARRAY_PRIMITIVE.read(msg))
|
||||
|
||||
if (!decryptedToken.contentEquals(handler.data!!.frontToken!!)) throw IllegalStateException("invalid token!")
|
||||
|
||||
val spec = SecretKeySpec(frontKey, "AES")
|
||||
val iv = IvParameterSpec(frontKey)
|
||||
val aesEn = Cipher.getInstance("AES/CFB8/NoPadding").let {
|
||||
it.init(Cipher.ENCRYPT_MODE, spec, iv)
|
||||
it
|
||||
}
|
||||
val aesDe = Cipher.getInstance("AES/CFB8/NoPadding").let {
|
||||
it.init(Cipher.DECRYPT_MODE, spec, iv)
|
||||
it
|
||||
}
|
||||
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
|
||||
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
|
||||
|
||||
handler.user.channel!!.pipeline().get(CloudEncryptor::class.java).cipher = aesEn
|
||||
handler.user.channel!!.pipeline().get(CloudDecryptor::class.java).cipher = aesDe
|
||||
@ -297,17 +285,6 @@ class LoginState : MinecraftConnectionState {
|
||||
it
|
||||
}
|
||||
|
||||
val backSpec = SecretKeySpec(backKey, "AES")
|
||||
val backIv = IvParameterSpec(backKey)
|
||||
val backAesEn = Cipher.getInstance("AES/CFB8/NoPadding").let {
|
||||
it.init(Cipher.ENCRYPT_MODE, backSpec, backIv)
|
||||
it
|
||||
}
|
||||
val backAesDe = Cipher.getInstance("AES/CFB8/NoPadding").let {
|
||||
it.init(Cipher.DECRYPT_MODE, backSpec, backIv)
|
||||
it
|
||||
}
|
||||
|
||||
val backHash = generateServerHash(handler.data!!.backServerId!!, backKey, handler.data!!.backPublicKey!!)
|
||||
|
||||
handler.user.channel!!.setAutoRead(false)
|
||||
@ -318,33 +295,30 @@ class LoginState : MinecraftConnectionState {
|
||||
"${UrlEscapers.urlFormParameterEscaper().escape(handler.data!!.backName!!)}&serverId=$frontHash")
|
||||
?: throw IllegalArgumentException("Couldn't authenticate with session servers")
|
||||
|
||||
var sent = false
|
||||
viaWebServer.listeners[fromUndashed(profile.get("id")!!.asString)]?.forEach {
|
||||
it.ws.send("""{"action": "session_hash_request", "user": "${handler.data!!.backName!!}", "session_hash": "$backHash",
|
||||
| "client_address": "${handler.user.channel!!.remoteAddress()}", "backend_public_key":
|
||||
| "${Base64.getEncoder().encodeToString(handler.data!!.backPublicKey!!.encoded)}"}""".trimMargin())
|
||||
it.ws.flush()
|
||||
sent = true
|
||||
}
|
||||
val sessionJoin = viaWebServer.requestSessionJoin(
|
||||
fromUndashed(profile.get("id")!!.asString),
|
||||
handler.data!!.backName!!,
|
||||
backHash,
|
||||
handler.address!!, // Frontend handler
|
||||
handler.data!!.backPublicKey!!
|
||||
)
|
||||
|
||||
if (!sent) {
|
||||
throw IllegalStateException("No connection to browser, connect in /auth.html")
|
||||
if (sessionJoin.first == 0) {
|
||||
throw IllegalStateException("No browsers listening to this account, connect in /auth.html")
|
||||
} else {
|
||||
viaWebServer.pendingSessionHashes.get(backHash).get(15, TimeUnit.SECONDS)
|
||||
sessionJoin.second.get(15, TimeUnit.SECONDS)
|
||||
val backChan = handler.other!!
|
||||
backChan.eventLoop().submit {
|
||||
val backMsg = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
backMsg.writeByte(1) // Packet id
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.ENCRYPT_MODE, handler.data!!.backPublicKey)
|
||||
it.doFinal(backKey)
|
||||
})
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.ENCRYPT_MODE, handler.data!!.backPublicKey)
|
||||
it.doFinal(handler.data!!.backToken)
|
||||
})
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, encryptRsa(handler.data!!.backPublicKey!!, backKey))
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, encryptRsa(handler.data!!.backPublicKey!!, handler.data!!.backToken!!))
|
||||
backChan.writeAndFlush(backMsg.retain())
|
||||
|
||||
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
|
||||
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
|
||||
|
||||
backChan.pipeline().get(CloudEncryptor::class.java).cipher = backAesEn
|
||||
backChan.pipeline().get(CloudDecryptor::class.java).cipher = backAesDe
|
||||
} finally {
|
||||
@ -418,3 +392,22 @@ class PlayState : MinecraftConnectionState {
|
||||
handler.user.disconnect(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fun decryptRsa(privateKey: PrivateKey, data: ByteArray) = Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.DECRYPT_MODE, privateKey)
|
||||
it.doFinal(data)
|
||||
}
|
||||
|
||||
fun encryptRsa(publicKey: PublicKey, data: ByteArray) = Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.ENCRYPT_MODE, publicKey)
|
||||
it.doFinal(data)
|
||||
}
|
||||
|
||||
fun mcCfb8(key: ByteArray, mode: Int) : Cipher {
|
||||
val spec = SecretKeySpec(key, "AES")
|
||||
val iv = IvParameterSpec(key)
|
||||
return Cipher.getInstance("AES/CFB8/NoPadding").let {
|
||||
it.init(mode, spec, iv)
|
||||
it
|
||||
}
|
||||
}
|
@ -101,8 +101,8 @@ object CloudAPI : ViaAPI<Unit> {
|
||||
}
|
||||
|
||||
object CloudPlatform : ViaPlatform<Unit> {
|
||||
val connMan = ViaConnectionManager()
|
||||
val executor = Executors.newCachedThreadPool(ThreadFactoryBuilder().setNameFormat("VIAaaS").setDaemon(true).build())
|
||||
val connMan = CloudConnectionManager()
|
||||
val executor = Executors.newCachedThreadPool(ThreadFactoryBuilder().setNameFormat("Via-%d").setDaemon(true).build())
|
||||
val eventLoop = DefaultEventLoop(executor)
|
||||
|
||||
init {
|
||||
@ -139,8 +139,12 @@ object CloudPlatform : ViaPlatform<Unit> {
|
||||
override fun getPlatformName(): String = "VIAaaS"
|
||||
override fun getPluginVersion(): String = VersionInfo.VERSION
|
||||
override fun isOldClientsAllowed(): Boolean = true
|
||||
override fun isProxy(): Boolean = true
|
||||
}
|
||||
|
||||
class CloudConnectionManager: ViaConnectionManager() {
|
||||
override fun isFrontEnd(conn: UserConnection): Boolean = false
|
||||
}
|
||||
|
||||
object CloudConfig : AbstractViaConfig(File("config/viaversion.yml")) {
|
||||
// https://github.com/ViaVersion/ViaFabric/blob/mc-1.16/src/main/java/com/github/creeper123123321/viafabric/platform/VRViaConfig.java
|
||||
|
@ -47,8 +47,9 @@ object CloudHeadProtocol : SimpleProtocol() {
|
||||
wrapper.write(Type.STRING, backAddr)
|
||||
wrapper.write(Type.UNSIGNED_SHORT, backPort)
|
||||
|
||||
val playerAddr = wrapper.user().channel!!.remoteAddress()
|
||||
logger.info("connecting $playerAddr ($playerVer) -> $backAddr:$backPort ($backProto)")
|
||||
val playerAddr = wrapper.user().channel!!.pipeline()
|
||||
.get(CloudMinecraftHandler::class.java)!!.address
|
||||
logger.info("Connecting $playerAddr ($playerVer) -> $backAddr:$backPort ($backProto)")
|
||||
|
||||
wrapper.user().put(CloudData(
|
||||
userConnection = wrapper.user(),
|
||||
|
@ -20,7 +20,9 @@ import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.event.Level
|
||||
import java.net.SocketAddress
|
||||
import java.net.URLEncoder
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
@ -101,7 +103,21 @@ class WebDashboardServer {
|
||||
|
||||
val pendingSessionHashes = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build<String, CompletableFuture<Void>>(CacheLoader.from { _ -> CompletableFuture() })
|
||||
.build<String, CompletableFuture<Unit>>(CacheLoader.from { _ -> CompletableFuture() })
|
||||
|
||||
suspend fun requestSessionJoin(id: UUID, name: String, hash: String,
|
||||
address: SocketAddress, backKey: PublicKey)
|
||||
: Pair<Int, CompletableFuture<Unit>> {
|
||||
var sent = 0
|
||||
viaWebServer.listeners[id]?.forEach {
|
||||
it.ws.send("""{"action": "session_hash_request", "user": "$name", "session_hash": "$hash",
|
||||
| "client_address": "$address", "backend_public_key":
|
||||
| "${Base64.getEncoder().encodeToString(backKey.encoded)}"}""".trimMargin())
|
||||
it.ws.flush()
|
||||
sent++
|
||||
}
|
||||
return sent to viaWebServer.pendingSessionHashes.get(hash)
|
||||
}
|
||||
|
||||
suspend fun connected(ws: WebSocketServerSession) {
|
||||
val loginState = WebLogin()
|
||||
|
@ -4,20 +4,34 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VIAaaS Authenticator</title>
|
||||
<style>body {font-family: sans-serif}</style>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif
|
||||
}
|
||||
@media (min-width: 700px) {
|
||||
#browser_accounts {
|
||||
float: right
|
||||
}
|
||||
}
|
||||
#browser_accounts {
|
||||
border: 1px solid black;
|
||||
padding: 10px
|
||||
}
|
||||
#connection_status {
|
||||
background: black;
|
||||
color: white
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.1/uuid.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>DO NOT TYPE YOUR CREDENTIALS IF YOU DON'T TRUST THIS VIAAAS INSTANCE OR THE CORS PROXY!</p>
|
||||
<p>Mojang API calls in browser are called through a CORS Proxy. See https://github.com/Rob--W/cors-anywhere
|
||||
for setting up one.</p>
|
||||
<label for="cors-proxy">CORS Proxy URL:</label>
|
||||
<div id="browser_accounts">
|
||||
<p>Browser Minecraft accounts:</p>
|
||||
<p><label for="cors-proxy">CORS Proxy URL:</label>
|
||||
<br>
|
||||
<input type="url" id="cors-proxy" name="cors-proxy" value="" onchange="localStorage.setItem('cors-proxy', this.value);">
|
||||
<script></script>
|
||||
<hr>
|
||||
<p>Browser Minecraft accounts:</p>
|
||||
</p>
|
||||
<p><span id="add-account">
|
||||
<label for="email">Email/Username (legacy):</label>
|
||||
<br>
|
||||
@ -29,9 +43,15 @@
|
||||
<input type="button" value="Login into Minecraft" onclick="loginMc()">
|
||||
</span></p>
|
||||
<span id="accounts"></span>
|
||||
<hr>
|
||||
</div>
|
||||
<div id="server_content">
|
||||
<p>DO NOT TYPE YOUR CREDENTIALS IF YOU DON'T TRUST THIS VIAAAS INSTANCE OR THE CORS PROXY!</p>
|
||||
<p>Mojang API calls in browser are called through a CORS Proxy. See https://github.com/Rob--W/cors-anywhere
|
||||
for setting up one. Calling Mojang API from a remote IP address may block your account.</p>
|
||||
<p>WebSocket connection status: <span id="connection_status">?</span></p>
|
||||
<hr>
|
||||
<p><span id="content"></span></p>
|
||||
</div>
|
||||
<script>
|
||||
let urlParams = new URLSearchParams();
|
||||
window.location.search.split("?").map(it => new URLSearchParams(it).forEach((a, b) => urlParams.append(b, a)));
|
||||
@ -167,6 +187,10 @@
|
||||
socket.send(JSON.stringify({"action": "listen_login_requests", "token": token}));
|
||||
}
|
||||
|
||||
function confirmJoin(hash) {
|
||||
socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash}));
|
||||
}
|
||||
|
||||
function saveToken(token) {
|
||||
let hTokens = JSON.parse(localStorage.getItem("tokens")) || {};
|
||||
let tokens = hTokens[wsUrl] || [];
|
||||
@ -241,7 +265,7 @@
|
||||
content.appendChild(p);
|
||||
} else if (parsed.action == "minecraft_id_result") {
|
||||
if (!parsed.success) {
|
||||
alert("Server couldn't verify account via Minecraft.ID");
|
||||
alert("VIAaaS instance couldn't verify account via Minecraft.ID");
|
||||
} else {
|
||||
listen(parsed.token);
|
||||
saveToken(parsed.token);
|
||||
@ -255,7 +279,7 @@
|
||||
removeToken(parsed.token);
|
||||
}
|
||||
} else if (parsed.action == "session_hash_request") {
|
||||
if (confirm("auth request sent from server, info: " + event.data + ". Should we authenticate?")) {
|
||||
if (confirm("Confirm auth request sent from VIAaaS instance? info: " + event.data)) {
|
||||
let accounts = getMcAccounts().filter(it => it.user.toLowerCase() == parsed.user.toLowerCase());
|
||||
accounts.forEach(it => {
|
||||
$.ajax({type: "post",
|
||||
@ -268,13 +292,15 @@
|
||||
contentType: "application/json",
|
||||
dataType: "json"
|
||||
}).done((data) => {
|
||||
socket.send(JSON.stringify({"action": "session_hash_response", "session_hash": parsed.session_hash}));
|
||||
confirmJoin(parsed.session_hash);
|
||||
}).fail((e) => {
|
||||
console.log(e);
|
||||
alert("Failed to authenticate to Minecraft backend server!");
|
||||
});
|
||||
});
|
||||
if (accounts.length == 0) alert("Couldn't find " + parsed.user + " account in browser");
|
||||
if (accounts.length == 0 && confirm("Couldn't find " + parsed.user + " account in browser. Continue without authentication (works on LAN worlds)?")) {
|
||||
confirmJoin(parsed.session_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user