some websockets and minecraft.id wip code

This commit is contained in:
creeper123123321 2020-10-25 01:13:18 -03:00
parent 4055d17858
commit f207ac2365
4 changed files with 150 additions and 11 deletions

View File

@ -34,6 +34,8 @@ dependencies {
implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-gson:$ktorVersion")
implementation("io.ktor:ktor-websockets:$ktorVersion")
implementation("ch.qos.logback:logback-classic:1.2.3")
testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")

View File

@ -2,6 +2,10 @@ package com.github.creeper123123321.viaaas
import de.gerrygames.viarewind.api.ViaRewindConfigImpl
import io.ktor.application.*
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.request.*
import io.ktor.network.tls.certificates.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
@ -18,6 +22,15 @@ import us.myles.ViaVersion.util.Config
import java.io.File
import java.net.InetAddress
val httpClient = HttpClient {
defaultRequest {
header("User-Agent", "VIAaaS")
}
install(JsonFeature) {
serializer = GsonSerializer()
}
}
fun main(args: Array<String>) {
File("config/https.jks").apply {
parentFile.mkdirs()
@ -61,6 +74,7 @@ fun main(args: Array<String>) {
}
ktorServer.stop(1000, 1000)
httpClient.close()
listOf<Future<*>>(future.channel().close(), boss.shutdownGracefully(), worker.shutdownGracefully())
.forEach { it.sync() }

View File

@ -1,14 +1,23 @@
package com.github.creeper123123321.viaaas
import com.google.common.cache.CacheBuilder
import com.google.gson.Gson
import com.google.gson.JsonObject
import io.ktor.application.*
import io.ktor.client.request.forms.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.http.cio.websocket.*
import io.ktor.http.content.*
import io.ktor.routing.*
import io.ktor.websocket.*
import kotlinx.coroutines.channels.consumeEach
import java.net.URLEncoder
import java.time.Duration
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import kotlin.collections.set
// todo https://minecraft.id/documentation
@ -24,13 +33,16 @@ class ViaWebApp {
routing {
webSocket("/ws") {
server.connected(this)
try {
server.connected(this)
incoming.consumeEach { frame ->
if (frame is Frame.Text) {
server.onMessage(this, frame.readText())
}
}
} catch (e: Exception) {
server.onException(this, e)
this.close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, e.toString()))
} finally {
server.disconnected(this)
}
@ -46,9 +58,14 @@ class ViaWebApp {
class WebDashboardServer {
val clients = ConcurrentHashMap<WebSocketSession, WebClient>()
val loginTokens = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.DAYS)
.build<UUID, String>()
val usernames = ConcurrentHashMap<String, WebClient>()
suspend fun connected(ws: WebSocketSession) {
val loginState = WebLogin()
val client = WebClient(ws, loginState)
val client = WebClient(this, ws, loginState)
clients[ws] = client
loginState.start(client)
}
@ -63,29 +80,80 @@ class WebDashboardServer {
client.state.disconnected(client)
clients.remove(ws)
}
suspend fun onException(ws: WebSocketSession, exception: java.lang.Exception) {
val client = clients[ws]!!
client.state.onException(client, exception)
}
}
data class WebClient(val ws: WebSocketSession, val state: WebState) {
}
data class WebClient(val server: WebDashboardServer,
val ws: WebSocketSession,
val state: WebState,
val listenedUsernames: MutableSet<String> = mutableSetOf())
interface WebState {
suspend fun start(webClient: WebClient)
suspend fun onMessage(webClient: WebClient, msg: String)
suspend fun disconnected(webClient: WebClient)
suspend fun onException(webClient: WebClient, exception: java.lang.Exception)
}
class WebLogin : WebState {
override suspend fun start(webClient: WebClient) {
webClient.ws.send("test")
webClient.ws.send("""{"action": "ad_minecraft_id_login"}""")
webClient.ws.flush()
}
override suspend fun onMessage(webClient: WebClient, msg: String) {
TODO("Not yet implemented")
val obj = Gson().fromJson<JsonObject>(msg, JsonObject::class.java)
when (obj.getAsJsonPrimitive("action").asString) {
"minecraft_id_login" -> {
val username = obj.getAsJsonPrimitive("username").asString
val code = obj.getAsJsonPrimitive("code").asString
val check = httpClient.submitForm<JsonObject>(
"https://api.minecraft.id/gateway/verify/${URLEncoder.encode(username, Charsets.UTF_8)}",
formParameters = parametersOf("code", code),
encodeInQuery = false) {
}
if (check.getAsJsonPrimitive("valid").asBoolean) {
val token = UUID.randomUUID()
webClient.server.loginTokens.put(token, username)
webClient.ws.send("""{"action": "minecraft_id_result", "success": true,
| "username": "$username", "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 != "") {
webClient.ws.send("""{"action": "listen_login_requests_result", "token": "$token", "success": true, "username": "$user"}""")
webClient.listenedUsernames.add(user)
webClient.server.usernames[user] = 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 }!!
}
else -> throw IllegalStateException("invalid action!")
}
webClient.ws.flush()
}
override suspend fun disconnected(webClient: WebClient) {
TODO("Not yet implemented")
webClient.listenedUsernames.forEach { webClient.server.usernames.remove(it, webClient) }
}
override suspend fun onException(webClient: WebClient, exception: java.lang.Exception) {
}
}

View File

@ -1,5 +1,4 @@
<!doctype html>
<!-- todo insert here online mode auth code with wss -->
<html lang="en">
<head>
<meta charset="UTF-8">
@ -10,33 +9,89 @@
</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
<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>Minecraft Login:</p>
<p>Minecraft Password:</p>
<p><span id="content"></span></p>
<script>
let urlParams = new URLSearchParams();
window.location.search.split("?").map(it => new URLSearchParams(it).forEach((a, b) => urlParams.append(b, a)));
history.replaceState(null, null, "auth.html");
var username = urlParams.get("username");
var mcauth_code = urlParams.get("mcauth_code");
if (urlParams.get("mcauth_success") == "false") {
alert("Couldn't authenticate with Minecraft.ID: " + urlParams.get("mcauth_msg"));
}
var socket = null;
var connectionStatus = document.getElementById("connection_status");
var content = document.getElementById("content");
function listen(token) {
socket.send('{"action": "listen_login_requests", "token": "' + token + '"}');
}
function connect() {
connectionStatus.innerText = "connecting...";
socket = new WebSocket("wss://" + window.location.host + "/ws");
socket.onerror = () => {
socket.onerror = e => {
console.log(e);
connectionStatus.innerText = "socket error";
content.innerHTML = "";
};
socket.onopen = () => {
connectionStatus.innerText = "connected";
content.innerHTML = "";
};
socket.onclose = evt => {
connectionStatus.innerText = "disconnected with close code " + evt.code + " and reason: " + evt.reason;
content.innerHTML = "";
setTimeout(connect, 5000);
};
socket.onmessage = event => {
console.log(event.data.toString());
let parsed = JSON.parse(event.data);
if (parsed.action == "ad_minecraft_id_login") {
let link = document.createElement("a");
link.innerText = "Prove your ownership of your accounts to this VIAaaS instance 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);
}
};
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");
} else {
listen(parsed.token);
let tokens = (localStorage.getItem("tokens") || "").split(",");
tokens.push(parsed.token);
localStorage.setItem("tokens", tokens.join(","));
}
} else if (parsed.action == "listen_login_requests_result") {
if (parsed.success) {
let msg = document.createElement("p");
msg.innerText = "Listening to logins with username: " + parsed.username;
content.appendChild(msg);
} else {
let tokens = (localStorage.getItem("tokens") || "").split(",");
tokens = tokens.filter(it => it != parsed.token);
localStorage.setItem("tokens", tokens.join(","));
}
}
};
}