mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-11-21 11:55:15 +01:00
wip online mode
This commit is contained in:
parent
f207ac2365
commit
fee83d594b
@ -18,6 +18,7 @@ import us.myles.ViaVersion.util.PipelineUtil
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.Deflater
|
||||
import java.util.zip.Inflater
|
||||
import javax.crypto.Cipher
|
||||
|
||||
|
||||
object ChannelInit : ChannelInitializer<Channel>() {
|
||||
@ -25,6 +26,8 @@ object ChannelInit : ChannelInitializer<Channel>() {
|
||||
val user = UserConnection(ch)
|
||||
CloudPipeline(user)
|
||||
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
||||
.addLast("encrypt", CloudEncryptor())
|
||||
.addLast("decrypt", CloudDecryptor())
|
||||
.addLast("frame-encoder", FrameEncoder)
|
||||
.addLast("frame-decoder", FrameDecoder())
|
||||
.addLast("compress", CloudCompressor())
|
||||
@ -36,9 +39,33 @@ object ChannelInit : ChannelInitializer<Channel>() {
|
||||
}
|
||||
}
|
||||
|
||||
class CloudDecryptor(var cipher: Cipher? = null) : MessageToMessageDecoder<ByteBuf>() {
|
||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||
val i = msg.readerIndex()
|
||||
val size = msg.readableBytes()
|
||||
if (cipher != null) {
|
||||
msg.writerIndex(i + cipher!!.update(msg.nioBuffer(), msg.nioBuffer(i, cipher!!.getOutputSize(size))))
|
||||
}
|
||||
out.add(msg.retain())
|
||||
}
|
||||
}
|
||||
|
||||
class CloudEncryptor(var cipher: Cipher? = null) : MessageToMessageEncoder<ByteBuf>() {
|
||||
override fun encode(ctx: ChannelHandlerContext?, msg: ByteBuf, out: MutableList<Any>) {
|
||||
val i = msg.readerIndex()
|
||||
val size = msg.readableBytes()
|
||||
if (cipher != null) {
|
||||
msg.writerIndex(i + cipher!!.update(msg.nioBuffer(), msg.nioBuffer(i, cipher!!.getOutputSize(size))))
|
||||
}
|
||||
out.add(msg.retain())
|
||||
}
|
||||
}
|
||||
|
||||
class BackendInit(val user: UserConnection) : ChannelInitializer<Channel>() {
|
||||
override fun initChannel(ch: Channel) {
|
||||
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
||||
.addLast("encrypt", CloudEncryptor())
|
||||
.addLast("decrypt", CloudDecryptor())
|
||||
.addLast("frame-encoder", FrameEncoder)
|
||||
.addLast("frame-decoder", FrameDecoder())
|
||||
.addLast("compress", CloudCompressor())
|
||||
|
@ -4,7 +4,6 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import de.gerrygames.viarewind.api.ViaRewindPlatform
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.channel.DefaultEventLoop
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
import nl.matsv.viabackwards.api.ViaBackwardsPlatform
|
||||
import us.myles.ViaVersion.AbstractViaConfig
|
||||
import us.myles.ViaVersion.api.Via
|
||||
@ -15,7 +14,6 @@ import us.myles.ViaVersion.api.boss.BossColor
|
||||
import us.myles.ViaVersion.api.boss.BossStyle
|
||||
import us.myles.ViaVersion.api.command.ViaCommandSender
|
||||
import us.myles.ViaVersion.api.configuration.ConfigurationProvider
|
||||
import us.myles.ViaVersion.api.data.StoredObject
|
||||
import us.myles.ViaVersion.api.data.UserConnection
|
||||
import us.myles.ViaVersion.api.platform.*
|
||||
import us.myles.ViaVersion.api.protocol.ProtocolRegistry
|
||||
@ -186,11 +184,4 @@ object CloudVersionProvider : VersionProvider() {
|
||||
if (ver != null) return ver
|
||||
return super.getServerProtocol(connection)
|
||||
}
|
||||
}
|
||||
|
||||
data class CloudData(val userConnection: UserConnection,
|
||||
var backendVer: Int,
|
||||
var backendChannel: SocketChannel? = null,
|
||||
var frontOnline: Boolean,
|
||||
var pendingStatus: Boolean = false
|
||||
) : StoredObject(userConnection)
|
||||
}
|
@ -1,13 +1,19 @@
|
||||
package com.github.creeper123123321.viaaas
|
||||
|
||||
import com.google.common.net.UrlEscapers
|
||||
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.ByteBufAllocator
|
||||
import io.netty.channel.Channel
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
import io.netty.channel.socket.nio.NioSocketChannel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import us.myles.ViaVersion.api.PacketWrapper
|
||||
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.protocol.Protocol
|
||||
import us.myles.ViaVersion.api.protocol.ProtocolPipeline
|
||||
@ -16,9 +22,20 @@ import us.myles.ViaVersion.api.protocol.SimpleProtocol
|
||||
import us.myles.ViaVersion.api.remapper.PacketRemapper
|
||||
import us.myles.ViaVersion.api.type.Type
|
||||
import us.myles.ViaVersion.packets.State
|
||||
import java.math.BigInteger
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.security.KeyFactory
|
||||
import java.security.MessageDigest
|
||||
import java.security.PublicKey
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.logging.Logger
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.naming.NameNotFoundException
|
||||
import javax.naming.directory.InitialDirContext
|
||||
|
||||
@ -127,22 +144,20 @@ object CloudHeadProtocol : SimpleProtocol() {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.registerOutgoing(State.LOGIN, 1, 1, object : PacketRemapper() {
|
||||
// encryption request
|
||||
override fun registerMap() {
|
||||
handler {
|
||||
val frontForwarder = it.user().channel!!.pipeline().get(CloudSideForwarder::class.java)
|
||||
it.cancel()
|
||||
frontForwarder.disconnect("Online mode in backend currently isn't compatible")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
object CloudTailProtocol : SimpleProtocol() {
|
||||
override fun registerPackets() {
|
||||
// Login start
|
||||
this.registerIncoming(State.LOGIN, 0, 0, object : PacketRemapper() {
|
||||
override fun registerMap() {
|
||||
handler {
|
||||
it.user().get(CloudData::class.java)!!.frontLoginName = it.passthrough(Type.STRING)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.registerOutgoing(State.LOGIN, 3, 3, object : PacketRemapper() {
|
||||
// set compression
|
||||
override fun registerMap() {
|
||||
@ -162,6 +177,131 @@ object CloudTailProtocol : SimpleProtocol() {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Crypto request
|
||||
this.registerOutgoing(State.LOGIN, 1, 1, object : PacketRemapper() {
|
||||
override fun registerMap() {
|
||||
map(Type.STRING) // Server id - unused
|
||||
map(Type.BYTE_ARRAY_PRIMITIVE) // Public key
|
||||
map(Type.BYTE_ARRAY_PRIMITIVE) // Token
|
||||
handler {
|
||||
val data = it.user().get(CloudData::class.java)!!
|
||||
data.backServerId = it.get(Type.STRING, 0)
|
||||
data.backPublicKey = KeyFactory.getInstance("RSA")
|
||||
.generatePublic(X509EncodedKeySpec(it.get(Type.BYTE_ARRAY_PRIMITIVE, 0)))
|
||||
data.backToken = it.get(Type.BYTE_ARRAY_PRIMITIVE, 1)
|
||||
|
||||
// We'll use non-vanilla server id, public key size and token size
|
||||
it.set(Type.STRING, 0, "VIAaaS")
|
||||
it.set(Type.BYTE_ARRAY_PRIMITIVE, 0, mcCryptoKey.public.encoded)
|
||||
val token = ByteArray(16)
|
||||
secureRandom.nextBytes(token)
|
||||
data.frontToken = token
|
||||
it.set(Type.BYTE_ARRAY_PRIMITIVE, 1, token.clone())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.registerIncoming(State.LOGIN, 1, 1, object : PacketRemapper() {
|
||||
override fun registerMap() {
|
||||
map(Type.BYTE_ARRAY_PRIMITIVE) // RSA shared secret
|
||||
map(Type.BYTE_ARRAY_PRIMITIVE) // RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
|
||||
handler { wrapper ->
|
||||
val data = wrapper.user().get(CloudData::class.java)!!
|
||||
|
||||
val encryptedSecret = wrapper.get(Type.BYTE_ARRAY_PRIMITIVE, 0)
|
||||
val secret = Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.DECRYPT_MODE, mcCryptoKey.private)
|
||||
it.doFinal(encryptedSecret)
|
||||
}
|
||||
val encryptedToken = wrapper.get(Type.BYTE_ARRAY_PRIMITIVE, 1)
|
||||
val decryptedToken = Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.DECRYPT_MODE, mcCryptoKey.private)
|
||||
it.doFinal(encryptedToken)
|
||||
}!!
|
||||
|
||||
if (!decryptedToken.contentEquals(data.frontToken!!)) throw IllegalStateException("invalid token!")
|
||||
|
||||
val spec = SecretKeySpec(secret, "AES")
|
||||
val iv = IvParameterSpec(secret)
|
||||
|
||||
val aesEn = Cipher.getInstance("AES/CFB8/NoPadding")
|
||||
val aesDe = Cipher.getInstance("AES/CFB8/NoPadding")
|
||||
aesEn.init(Cipher.ENCRYPT_MODE, spec, iv)
|
||||
aesDe.init(Cipher.DECRYPT_MODE, spec, iv)
|
||||
|
||||
wrapper.user().channel!!.pipeline().get(CloudEncryptor::class.java).cipher = aesEn
|
||||
wrapper.user().channel!!.pipeline().get(CloudDecryptor::class.java).cipher = aesDe
|
||||
|
||||
val frontHash = generateServerHash("VIAaaS", secret, mcCryptoKey.public)
|
||||
|
||||
val backKey = ByteArray(16)
|
||||
secureRandom.nextBytes(backKey)
|
||||
|
||||
val backSpec = SecretKeySpec(secret, "AES")
|
||||
val backIv = IvParameterSpec(secret)
|
||||
|
||||
val backAesEn = Cipher.getInstance("AES/CFB8/NoPadding")
|
||||
val backAesDe = Cipher.getInstance("AES/CFB8/NoPadding")
|
||||
backAesEn.init(Cipher.ENCRYPT_MODE, backSpec, backIv)
|
||||
backAesDe.init(Cipher.DECRYPT_MODE, backSpec, backIv)
|
||||
|
||||
val backHash = generateServerHash(data.backServerId!!, backKey, data.backPublicKey!!)
|
||||
|
||||
wrapper.cancel()
|
||||
Via.getPlatform().runAsync {
|
||||
// Don't need to disable autoread, server will wait us
|
||||
runBlocking {
|
||||
try {
|
||||
val profile = httpClient.get<JsonObject?>("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
|
||||
"${UrlEscapers.urlFormParameterEscaper().escape(data.frontLoginName!!)}&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", "session_hash": "$backHash",
|
||||
| "client_address": "${wrapper.user().channel!!.remoteAddress()}", "backend_public_key":
|
||||
| "${Base64.getEncoder().encodeToString(data.backPublicKey!!.encoded)}"}""".trimMargin())
|
||||
it.ws.flush()
|
||||
sent = true
|
||||
}
|
||||
|
||||
if (!sent) {
|
||||
throw IllegalStateException("No connection to browser, connect in /auth.html")
|
||||
} else {
|
||||
viaWebServer.pendingSessionHashes.get(backHash).get(15, TimeUnit.SECONDS)
|
||||
wrapper.user().channel!!.eventLoop().submit {
|
||||
val backCrypto = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
backCrypto.writeByte(1) // Packet id
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backCrypto, Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.ENCRYPT_MODE, data.backPublicKey)
|
||||
it.doFinal(backKey)
|
||||
})
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backCrypto, Cipher.getInstance("RSA").let {
|
||||
it.init(Cipher.ENCRYPT_MODE, data.backPublicKey)
|
||||
it.doFinal(data.backToken)
|
||||
})
|
||||
val backChan = wrapper.user().channel!!.pipeline()
|
||||
.get(CloudSideForwarder::class.java).other!!
|
||||
backChan.writeAndFlush(backCrypto.retain())
|
||||
backChan.pipeline().get(CloudEncryptor::class.java).cipher = backAesEn
|
||||
backChan.pipeline().get(CloudDecryptor::class.java).cipher = backAesDe
|
||||
} finally {
|
||||
backCrypto.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
wrapper.user().channel!!.pipeline()
|
||||
.get(CloudSideForwarder::class.java)
|
||||
.disconnect("Online mode error: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,3 +309,29 @@ fun Channel.setAutoRead(b: Boolean) {
|
||||
this.config().isAutoRead = b
|
||||
if (b) this.read()
|
||||
}
|
||||
|
||||
val secureRandom = SecureRandom.getInstanceStrong()
|
||||
|
||||
fun twosComplementHexdigest(digest: ByteArray): String {
|
||||
return BigInteger(digest).toString(16)
|
||||
}
|
||||
|
||||
// https://github.com/VelocityPowered/Velocity/blob/0dd6fe1ef2783fe1f9322af06c6fd218aa67cdb1/proxy/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java
|
||||
fun generateServerHash(serverId: String, sharedSecret: ByteArray?, key: PublicKey): String {
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
digest.update(serverId.toByteArray(Charsets.ISO_8859_1))
|
||||
digest.update(sharedSecret)
|
||||
digest.update(key.encoded)
|
||||
return twosComplementHexdigest(digest.digest())
|
||||
}
|
||||
|
||||
data class CloudData(val userConnection: UserConnection,
|
||||
var backendVer: Int,
|
||||
var frontOnline: Boolean,
|
||||
var pendingStatus: Boolean = false,
|
||||
var backServerId: String? = null,
|
||||
var backPublicKey: PublicKey? = null,
|
||||
var backToken: ByteArray? = null,
|
||||
var frontToken: ByteArray? = null,
|
||||
var frontLoginName: String? = null
|
||||
) : StoredObject(userConnection)
|
@ -21,6 +21,7 @@ import us.myles.ViaVersion.api.protocol.ProtocolVersion
|
||||
import us.myles.ViaVersion.util.Config
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import java.security.KeyPairGenerator
|
||||
|
||||
val httpClient = HttpClient {
|
||||
defaultRequest {
|
||||
@ -31,6 +32,12 @@ val httpClient = HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Minecraft doesn't have forward secrecy
|
||||
val mcCryptoKey = KeyPairGenerator.getInstance("RSA").let {
|
||||
it.initialize(4096) // https://stackoverflow.com/questions/1904516/is-1024-bit-rsa-secure
|
||||
it.genKeyPair()
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
File("config/https.jks").apply {
|
||||
parentFile.mkdirs()
|
||||
|
@ -1,9 +1,12 @@
|
||||
package com.github.creeper123123321.viaaas
|
||||
|
||||
import com.google.common.base.Preconditions
|
||||
import com.google.common.cache.CacheBuilder
|
||||
import com.google.common.cache.CacheLoader
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import io.ktor.application.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
@ -12,18 +15,22 @@ import io.ktor.http.content.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.websocket.*
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.net.URLEncoder
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.set
|
||||
|
||||
|
||||
// todo https://minecraft.id/documentation
|
||||
|
||||
class ViaWebApp {
|
||||
val server = WebDashboardServer()
|
||||
val viaWebServer = WebDashboardServer()
|
||||
|
||||
class ViaWebApp {
|
||||
fun Application.main() {
|
||||
install(DefaultHeaders)
|
||||
install(CallLogging)
|
||||
@ -34,17 +41,18 @@ class ViaWebApp {
|
||||
routing {
|
||||
webSocket("/ws") {
|
||||
try {
|
||||
server.connected(this)
|
||||
viaWebServer.connected(this)
|
||||
incoming.consumeEach { frame ->
|
||||
if (frame is Frame.Text) {
|
||||
server.onMessage(this, frame.readText())
|
||||
viaWebServer.onMessage(this, frame.readText())
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
server.onException(this, e)
|
||||
e.printStackTrace()
|
||||
viaWebServer.onException(this, e)
|
||||
this.close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, e.toString()))
|
||||
} finally {
|
||||
server.disconnected(this)
|
||||
viaWebServer.disconnected(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,12 +64,35 @@ class ViaWebApp {
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/VelocityPowered/Velocity/blob/6467335f74a7d1617512a55cc9acef5e109b51ac/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java
|
||||
fun fromUndashed(string: String): UUID {
|
||||
Preconditions.checkArgument(string.length == 32, "Length is incorrect")
|
||||
return UUID(
|
||||
java.lang.Long.parseUnsignedLong(string.substring(0, 16), 16),
|
||||
java.lang.Long.parseUnsignedLong(string.substring(16), 16)
|
||||
)
|
||||
}
|
||||
|
||||
class WebDashboardServer {
|
||||
val clients = ConcurrentHashMap<WebSocketSession, WebClient>()
|
||||
val loginTokens = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.DAYS)
|
||||
.build<UUID, String>()
|
||||
val usernames = ConcurrentHashMap<String, WebClient>()
|
||||
.build<UUID, UUID>()
|
||||
|
||||
// Minecraft account -> WebClient
|
||||
val listeners = ConcurrentHashMap<UUID, MutableSet<WebClient>>()
|
||||
val usernameIdCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build<String, UUID>(CacheLoader.from { name ->
|
||||
runBlocking {
|
||||
httpClient.get<JsonObject?>("https://api.mojang.com/users/profiles/minecraft/$name")
|
||||
?.get("id")?.asString?.let { fromUndashed(it) }
|
||||
}
|
||||
})
|
||||
|
||||
val pendingSessionHashes = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build<String, CompletableFuture<Void>>(CacheLoader.from { _ -> CompletableFuture() })
|
||||
|
||||
suspend fun connected(ws: WebSocketSession) {
|
||||
val loginState = WebLogin()
|
||||
@ -91,7 +122,7 @@ class WebDashboardServer {
|
||||
data class WebClient(val server: WebDashboardServer,
|
||||
val ws: WebSocketSession,
|
||||
val state: WebState,
|
||||
val listenedUsernames: MutableSet<String> = mutableSetOf())
|
||||
val listenedIds: MutableSet<UUID> = mutableSetOf())
|
||||
|
||||
interface WebState {
|
||||
suspend fun start(webClient: WebClient)
|
||||
@ -120,29 +151,34 @@ class WebLogin : WebState {
|
||||
encodeInQuery = false) {
|
||||
}
|
||||
|
||||
|
||||
if (check.getAsJsonPrimitive("valid").asBoolean) {
|
||||
val token = UUID.randomUUID()
|
||||
webClient.server.loginTokens.put(token, username)
|
||||
val mcIdUser = check.get("username").asString
|
||||
val uuid = webClient.server.usernameIdCache.get(mcIdUser)
|
||||
|
||||
webClient.server.loginTokens.put(token, uuid)
|
||||
webClient.ws.send("""{"action": "minecraft_id_result", "success": true,
|
||||
| "username": "$username", "token": "$token"}""".trimMargin())
|
||||
| "username": "$mcIdUser", "uuid": "$uuid", "token": "$token"}""".trimMargin())
|
||||
} else {
|
||||
webClient.ws.send("""{"action": "minecraft_id_result", "success": false}""")
|
||||
}
|
||||
}
|
||||
"listen_login_requests" -> {
|
||||
val token = UUID.fromString(obj.getAsJsonPrimitive("token").asString)
|
||||
val user = webClient.server.loginTokens.get(token) { "" }
|
||||
if (user != "") {
|
||||
val user = webClient.server.loginTokens.getIfPresent(token)
|
||||
if (user != null) {
|
||||
webClient.ws.send("""{"action": "listen_login_requests_result", "token": "$token", "success": true, "username": "$user"}""")
|
||||
webClient.listenedUsernames.add(user)
|
||||
webClient.server.usernames[user] = webClient
|
||||
webClient.listenedIds.add(user)
|
||||
webClient.server.listeners.computeIfAbsent(user) { Collections.newSetFromMap(ConcurrentHashMap()) }
|
||||
.add(webClient)
|
||||
} else {
|
||||
webClient.ws.send("""{"action": "listen_login_requests_result", "token": "$token", "success": false}""")
|
||||
}
|
||||
}
|
||||
"session_hash_response" -> {
|
||||
val token = UUID.fromString(obj.getAsJsonPrimitive("token").asString)
|
||||
val user = webClient.server.loginTokens.get(token) { null }!!
|
||||
val hash = obj.get("session_hash").asString
|
||||
webClient.server.pendingSessionHashes.getIfPresent(hash)?.complete(null)
|
||||
}
|
||||
else -> throw IllegalStateException("invalid action!")
|
||||
}
|
||||
@ -151,7 +187,7 @@ class WebLogin : WebState {
|
||||
}
|
||||
|
||||
override suspend fun disconnected(webClient: WebClient) {
|
||||
webClient.listenedUsernames.forEach { webClient.server.usernames.remove(it, webClient) }
|
||||
webClient.listenedIds.forEach { webClient.server.listeners[it]?.remove(webClient) }
|
||||
}
|
||||
|
||||
override suspend fun onException(webClient: WebClient, exception: java.lang.Exception) {
|
||||
|
@ -3,15 +3,15 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VIAaaS</title>
|
||||
<title>VIAaaS Authenticator</title>
|
||||
<style>
|
||||
body {font-family: sans-serif}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
WIP todo insert here online mode auth code with wss, store login things in the browser, make viaaas ask the browser to contact session servers
|
||||
WIP TODO - authenticate to backend server via browser, proxy the Mojang Login API, does this work on 1.7 and other versions?
|
||||
<p>WebSocket connection status: <span id="connection_status">?</span></p>
|
||||
<p>DO NOT TYPE YOUR CREDENTIALS IF YOU DON'T TRUST THE CONNECTION TO THIS VIAAAS INSTANCE!</p>
|
||||
<p>DO NOT TYPE YOUR CREDENTIALS IF YOU DON'T TRUST THIS VIAAAS INSTANCE!</p>
|
||||
<p><span id="content"></span></p>
|
||||
<script>
|
||||
let urlParams = new URLSearchParams();
|
||||
@ -44,6 +44,8 @@ WIP todo insert here online mode auth code with wss, store login things in the b
|
||||
socket.onopen = () => {
|
||||
connectionStatus.innerText = "connected";
|
||||
content.innerHTML = "";
|
||||
|
||||
(localStorage.getItem("tokens") || "").split(",").filter(it => it != "").forEach(listen);
|
||||
};
|
||||
|
||||
socket.onclose = evt => {
|
||||
@ -56,22 +58,24 @@ WIP todo insert here online mode auth code with wss, store login things in the b
|
||||
console.log(event.data.toString());
|
||||
let parsed = JSON.parse(event.data);
|
||||
if (parsed.action == "ad_minecraft_id_login") {
|
||||
if (username != null && mcauth_code != null) {
|
||||
let add = document.createElement("a");
|
||||
add.innerText = "Add account " + username;
|
||||
add.href = "javascript:";
|
||||
add.onclick = () => {
|
||||
socket.send('{"action": "minecraft_id_login", "username": "' + username + '", "code": "' + mcauth_code + '"}');
|
||||
};
|
||||
content.appendChild(add);
|
||||
}
|
||||
let link = document.createElement("a");
|
||||
link.innerText = "Prove your ownership of your accounts to this VIAaaS instance with Minecraft.ID";
|
||||
link.innerText = "Add account with Minecraft.ID";
|
||||
link.href = "javascript:";
|
||||
link.onclick = () => {
|
||||
if (username != null && mcauth_code != null) {
|
||||
socket.send('{"action": "minecraft_id_login", "username": "' + username + '", "code": "' + mcauth_code + '"}');
|
||||
username = null; mcauth_code = null;
|
||||
} else {
|
||||
let user = prompt("Username: ", "");
|
||||
let callbackUrl = new URL(location.origin + location.pathname + "?username=" + encodeURIComponent(user));
|
||||
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user) + "?callback=" + encodeURIComponent(callbackUrl);
|
||||
}
|
||||
let user = prompt("Username: ", "");
|
||||
let callbackUrl = new URL(location.origin + location.pathname + "?username=" + encodeURIComponent(user));
|
||||
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user) + "?callback=" + encodeURIComponent(callbackUrl);
|
||||
};
|
||||
content.appendChild(link);
|
||||
|
||||
(localStorage.getItem("tokens") || "").split(",").filter(it => it != "").forEach(listen);
|
||||
} else if (parsed.action == "minecraft_id_result") {
|
||||
if (!parsed.success) {
|
||||
alert("Server couldn't verify account via Minecraft.ID");
|
||||
@ -91,6 +95,9 @@ WIP todo insert here online mode auth code with wss, store login things in the b
|
||||
tokens = tokens.filter(it => it != parsed.token);
|
||||
localStorage.setItem("tokens", tokens.join(","));
|
||||
}
|
||||
} else if (parsed.action == "session_hash_request") {
|
||||
alert("TODO!"); // todo
|
||||
socket.send('{"action": "session_hash_response", "session_hash": "' + parsed.session_hash + '"}');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user