mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2025-02-16 01:41:24 +01:00
some websockets and minecraft.id wip code
This commit is contained in:
parent
4055d17858
commit
f207ac2365
@ -34,6 +34,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
|
implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-server-netty:$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("io.ktor:ktor-websockets:$ktorVersion")
|
||||||
implementation("ch.qos.logback:logback-classic:1.2.3")
|
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||||
testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")
|
testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")
|
||||||
|
@ -2,6 +2,10 @@ package com.github.creeper123123321.viaaas
|
|||||||
|
|
||||||
import de.gerrygames.viarewind.api.ViaRewindConfigImpl
|
import de.gerrygames.viarewind.api.ViaRewindConfigImpl
|
||||||
import io.ktor.application.*
|
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.network.tls.certificates.*
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
import io.ktor.server.netty.*
|
import io.ktor.server.netty.*
|
||||||
@ -18,6 +22,15 @@ import us.myles.ViaVersion.util.Config
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
val httpClient = HttpClient {
|
||||||
|
defaultRequest {
|
||||||
|
header("User-Agent", "VIAaaS")
|
||||||
|
}
|
||||||
|
install(JsonFeature) {
|
||||||
|
serializer = GsonSerializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
File("config/https.jks").apply {
|
File("config/https.jks").apply {
|
||||||
parentFile.mkdirs()
|
parentFile.mkdirs()
|
||||||
@ -61,6 +74,7 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ktorServer.stop(1000, 1000)
|
ktorServer.stop(1000, 1000)
|
||||||
|
httpClient.close()
|
||||||
listOf<Future<*>>(future.channel().close(), boss.shutdownGracefully(), worker.shutdownGracefully())
|
listOf<Future<*>>(future.channel().close(), boss.shutdownGracefully(), worker.shutdownGracefully())
|
||||||
.forEach { it.sync() }
|
.forEach { it.sync() }
|
||||||
|
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
package com.github.creeper123123321.viaaas
|
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.application.*
|
||||||
|
import io.ktor.client.request.forms.*
|
||||||
import io.ktor.features.*
|
import io.ktor.features.*
|
||||||
|
import io.ktor.http.*
|
||||||
import io.ktor.http.cio.websocket.*
|
import io.ktor.http.cio.websocket.*
|
||||||
import io.ktor.http.content.*
|
import io.ktor.http.content.*
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import io.ktor.websocket.*
|
import io.ktor.websocket.*
|
||||||
import kotlinx.coroutines.channels.consumeEach
|
import kotlinx.coroutines.channels.consumeEach
|
||||||
|
import java.net.URLEncoder
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
// todo https://minecraft.id/documentation
|
// todo https://minecraft.id/documentation
|
||||||
|
|
||||||
@ -24,13 +33,16 @@ class ViaWebApp {
|
|||||||
|
|
||||||
routing {
|
routing {
|
||||||
webSocket("/ws") {
|
webSocket("/ws") {
|
||||||
server.connected(this)
|
|
||||||
try {
|
try {
|
||||||
|
server.connected(this)
|
||||||
incoming.consumeEach { frame ->
|
incoming.consumeEach { frame ->
|
||||||
if (frame is Frame.Text) {
|
if (frame is Frame.Text) {
|
||||||
server.onMessage(this, frame.readText())
|
server.onMessage(this, frame.readText())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
server.onException(this, e)
|
||||||
|
this.close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, e.toString()))
|
||||||
} finally {
|
} finally {
|
||||||
server.disconnected(this)
|
server.disconnected(this)
|
||||||
}
|
}
|
||||||
@ -46,9 +58,14 @@ class ViaWebApp {
|
|||||||
|
|
||||||
class WebDashboardServer {
|
class WebDashboardServer {
|
||||||
val clients = ConcurrentHashMap<WebSocketSession, WebClient>()
|
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) {
|
suspend fun connected(ws: WebSocketSession) {
|
||||||
val loginState = WebLogin()
|
val loginState = WebLogin()
|
||||||
val client = WebClient(ws, loginState)
|
val client = WebClient(this, ws, loginState)
|
||||||
clients[ws] = client
|
clients[ws] = client
|
||||||
loginState.start(client)
|
loginState.start(client)
|
||||||
}
|
}
|
||||||
@ -63,29 +80,80 @@ class WebDashboardServer {
|
|||||||
client.state.disconnected(client)
|
client.state.disconnected(client)
|
||||||
clients.remove(ws)
|
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 {
|
interface WebState {
|
||||||
suspend fun start(webClient: WebClient)
|
suspend fun start(webClient: WebClient)
|
||||||
suspend fun onMessage(webClient: WebClient, msg: String)
|
suspend fun onMessage(webClient: WebClient, msg: String)
|
||||||
suspend fun disconnected(webClient: WebClient)
|
suspend fun disconnected(webClient: WebClient)
|
||||||
|
suspend fun onException(webClient: WebClient, exception: java.lang.Exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebLogin : WebState {
|
class WebLogin : WebState {
|
||||||
override suspend fun start(webClient: WebClient) {
|
override suspend fun start(webClient: WebClient) {
|
||||||
webClient.ws.send("test")
|
webClient.ws.send("""{"action": "ad_minecraft_id_login"}""")
|
||||||
webClient.ws.flush()
|
webClient.ws.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun onMessage(webClient: WebClient, msg: String) {
|
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) {
|
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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<!-- todo insert here online mode auth code with wss -->
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@ -10,33 +9,89 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>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 THE CONNECTION TO THIS VIAAAS INSTANCE!</p>
|
||||||
<p>Minecraft Login:</p>
|
<p><span id="content"></span></p>
|
||||||
<p>Minecraft Password:</p>
|
|
||||||
<script>
|
<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 socket = null;
|
||||||
var connectionStatus = document.getElementById("connection_status");
|
var connectionStatus = document.getElementById("connection_status");
|
||||||
|
var content = document.getElementById("content");
|
||||||
|
|
||||||
|
function listen(token) {
|
||||||
|
socket.send('{"action": "listen_login_requests", "token": "' + token + '"}');
|
||||||
|
}
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
connectionStatus.innerText = "connecting...";
|
connectionStatus.innerText = "connecting...";
|
||||||
socket = new WebSocket("wss://" + window.location.host + "/ws");
|
socket = new WebSocket("wss://" + window.location.host + "/ws");
|
||||||
|
|
||||||
socket.onerror = () => {
|
socket.onerror = e => {
|
||||||
|
console.log(e);
|
||||||
connectionStatus.innerText = "socket error";
|
connectionStatus.innerText = "socket error";
|
||||||
|
content.innerHTML = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
connectionStatus.innerText = "connected";
|
connectionStatus.innerText = "connected";
|
||||||
|
content.innerHTML = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onclose = evt => {
|
socket.onclose = evt => {
|
||||||
connectionStatus.innerText = "disconnected with close code " + evt.code + " and reason: " + evt.reason;
|
connectionStatus.innerText = "disconnected with close code " + evt.code + " and reason: " + evt.reason;
|
||||||
|
content.innerHTML = "";
|
||||||
setTimeout(connect, 5000);
|
setTimeout(connect, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onmessage = event => {
|
socket.onmessage = event => {
|
||||||
console.log(event.data.toString());
|
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(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user