mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-11-15 10:55:27 +01:00
native transport, refresh account only when used, console command completing
closes #8 closes #9
This commit is contained in:
parent
7d4ee38392
commit
bb7778d9a9
@ -1,6 +1,7 @@
|
||||
VIAaaS
|
||||
---
|
||||
Idea: server.example.com._p25565._v1_12_2._otrue.viaaas.example.com (default backend 25565 port and version default as auto, online-mode can be optional/required) (similar to tor to web proxies)
|
||||
Idea: server.example.com._p25565._v1_12_2._otrue._uBACKUSERNAME.viaaas.example.com (default backend 25565 port and version
|
||||
default as auto, online-mode can be optional/required) (similar to tor to web proxies)
|
||||
|
||||
- TODO: _o option for disabling online mode only in front end, protocol auto detection
|
||||
|
||||
@ -18,8 +19,10 @@ Usage for offline mode:
|
||||
|
||||
Usage for online mode (may block your Mojang account):
|
||||
- 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)).
|
||||
- You'll need 2 premium accounts for online mode (using only one account is possible but, as only one access tokens
|
||||
can be active, your Minecraft client will give Bad Login after you approve the login)
|
||||
- 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.
|
||||
- Log in into Minecraft account with the username you'll use in _u option via browser.
|
||||
- Connect to mc.example.com._v1_8.viaaas._u(BACKUSERNAME).localhost
|
||||
|
@ -12,7 +12,6 @@ import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.SimpleChannelInboundHandler
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
import io.netty.channel.socket.nio.NioSocketChannel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@ -145,7 +144,7 @@ class HandshakeState : MinecraftConnectionState {
|
||||
|| addrInfo.isAnyLocalAddress) throw SecurityException("Local addresses aren't allowed")
|
||||
|
||||
val bootstrap = Bootstrap().handler(BackendInit(handler.user))
|
||||
.channel(NioSocketChannel::class.java)
|
||||
.channelFactory(channelSocketFactory())
|
||||
.group(handler.user.channel!!.eventLoop())
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
||||
.connect(socketAddr)
|
||||
@ -403,7 +402,7 @@ fun encryptRsa(publicKey: PublicKey, data: ByteArray) = Cipher.getInstance("RSA"
|
||||
it.doFinal(data)
|
||||
}
|
||||
|
||||
fun mcCfb8(key: ByteArray, mode: Int) : Cipher {
|
||||
fun mcCfb8(key: ByteArray, mode: Int): Cipher {
|
||||
val spec = SecretKeySpec(key, "AES")
|
||||
val iv = IvParameterSpec(key)
|
||||
return Cipher.getInstance("AES/CFB8/NoPadding").let {
|
||||
|
@ -10,11 +10,27 @@ import io.ktor.network.tls.certificates.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.netty.bootstrap.ServerBootstrap
|
||||
import io.netty.channel.ChannelFactory
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.EventLoopGroup
|
||||
import io.netty.channel.epoll.Epoll
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel
|
||||
import io.netty.channel.epoll.EpollSocketChannel
|
||||
import io.netty.channel.kqueue.KQueue
|
||||
import io.netty.channel.kqueue.KQueueEventLoopGroup
|
||||
import io.netty.channel.kqueue.KQueueServerSocketChannel
|
||||
import io.netty.channel.kqueue.KQueueSocketChannel
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import io.netty.channel.socket.ServerSocketChannel
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||
import io.netty.channel.socket.nio.NioSocketChannel
|
||||
import io.netty.util.concurrent.Future
|
||||
import net.minecrell.terminalconsole.SimpleTerminalConsole
|
||||
import org.jline.reader.Candidate
|
||||
import org.jline.reader.LineReader
|
||||
import org.jline.reader.LineReaderBuilder
|
||||
import org.slf4j.LoggerFactory
|
||||
import us.myles.ViaVersion.ViaManager
|
||||
import us.myles.ViaVersion.api.Via
|
||||
@ -23,7 +39,6 @@ import us.myles.ViaVersion.api.data.MappingDataLoader
|
||||
import us.myles.ViaVersion.api.protocol.ProtocolVersion
|
||||
import us.myles.ViaVersion.util.Config
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.net.InetAddress
|
||||
import java.security.KeyPairGenerator
|
||||
import java.util.*
|
||||
@ -49,6 +64,30 @@ val mcCryptoKey = KeyPairGenerator.getInstance("RSA").let {
|
||||
it.genKeyPair()
|
||||
}
|
||||
|
||||
fun eventLoopGroup(): EventLoopGroup {
|
||||
if (VIAaaSConfig.isNativeTransportMc) {
|
||||
if (Epoll.isAvailable()) return EpollEventLoopGroup()
|
||||
if (KQueue.isAvailable()) return KQueueEventLoopGroup()
|
||||
}
|
||||
return NioEventLoopGroup()
|
||||
}
|
||||
|
||||
fun channelServerSocketFactory(): ChannelFactory<ServerSocketChannel> {
|
||||
if (VIAaaSConfig.isNativeTransportMc) {
|
||||
if (Epoll.isAvailable()) return ChannelFactory { EpollServerSocketChannel() }
|
||||
if (KQueue.isAvailable()) return ChannelFactory { KQueueServerSocketChannel() }
|
||||
}
|
||||
return ChannelFactory { NioServerSocketChannel() }
|
||||
}
|
||||
|
||||
fun channelSocketFactory(): ChannelFactory<SocketChannel> {
|
||||
if (VIAaaSConfig.isNativeTransportMc) {
|
||||
if (Epoll.isAvailable()) return ChannelFactory { EpollSocketChannel() }
|
||||
if (KQueue.isAvailable()) return ChannelFactory { KQueueSocketChannel() }
|
||||
}
|
||||
return ChannelFactory { NioSocketChannel() }
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
File("config/https.jks").apply {
|
||||
parentFile.mkdirs()
|
||||
@ -65,10 +104,12 @@ fun main(args: Array<String>) {
|
||||
CloudRewind.init(ViaRewindConfigImpl(File("config/viarewind.yml")))
|
||||
CloudBackwards.init(File("config/viabackwards.yml"))
|
||||
|
||||
val boss = NioEventLoopGroup()
|
||||
val worker = NioEventLoopGroup()
|
||||
val future = ServerBootstrap().group(boss, worker)
|
||||
.channel(NioServerSocketChannel::class.java)
|
||||
val parent = eventLoopGroup()
|
||||
val child = eventLoopGroup()
|
||||
|
||||
val future = ServerBootstrap()
|
||||
.group(parent, child)
|
||||
.channelFactory(channelServerSocketFactory())
|
||||
.childHandler(ChannelInit)
|
||||
.childOption(ChannelOption.IP_TOS, 0x18)
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
@ -91,28 +132,65 @@ fun main(args: Array<String>) {
|
||||
|
||||
ktorServer?.stop(1000, 1000)
|
||||
httpClient.close()
|
||||
listOf<Future<*>>(future.channel().close(), boss.shutdownGracefully(), worker.shutdownGracefully())
|
||||
listOf<Future<*>>(future.channel().close(), parent.shutdownGracefully(), child.shutdownGracefully())
|
||||
.forEach { it.sync() }
|
||||
|
||||
Via.getManager().destroy()
|
||||
}
|
||||
|
||||
class VIAaaSConsole : SimpleTerminalConsole(), ViaCommandSender {
|
||||
val commands = hashMapOf<String, (String, Array<String>) -> Unit>()
|
||||
val commands = hashMapOf<String, (MutableList<String>?, String, Array<String>) -> Unit>()
|
||||
override fun isRunning(): Boolean = runningServer
|
||||
|
||||
init {
|
||||
commands["stop"] = { _, _ -> this.shutdown() }
|
||||
commands["stop"] = { suggestion, _, _ -> if (suggestion == null) this.shutdown() }
|
||||
commands["end"] = commands["stop"]!!
|
||||
commands["viaversion"] = { _, args ->
|
||||
Via.getManager().commandHandler.onCommand(this, args)
|
||||
commands["viaversion"] = { suggestion, _, args ->
|
||||
if (suggestion == null) {
|
||||
Via.getManager().commandHandler.onCommand(this, args)
|
||||
} else {
|
||||
suggestion.addAll(Via.getManager().commandHandler.onTabComplete(this, args))
|
||||
}
|
||||
}
|
||||
commands["viaver"] = commands["viaversion"]!!
|
||||
commands["vvcloud"] = commands["viaversion"]!!
|
||||
commands["help"] = { _, _ ->
|
||||
sendMessage(commands.keys.toString())
|
||||
commands["help"] = { suggestion , _, _ ->
|
||||
if (suggestion == null) sendMessage(commands.keys.toString())
|
||||
}
|
||||
commands["?"] = commands["help"]!!
|
||||
commands["list"] = { suggestion, _, _ ->
|
||||
if (suggestion == null) {
|
||||
Via.getPlatform().connectionManager.connections.forEach {
|
||||
sendMessage("${it.channel?.remoteAddress()} (${it.protocolInfo?.protocolVersion}) -> " +
|
||||
"(${it.protocolInfo?.serverProtocolVersion}) " +
|
||||
"${it.channel?.pipeline()?.get(CloudMinecraftHandler::class.java)?.other?.remoteAddress()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildReader(builder: LineReaderBuilder): LineReader {
|
||||
// Stolen from Velocity
|
||||
return super.buildReader(builder.appName("VIAaaS").completer { _, line, candidates ->
|
||||
try {
|
||||
val cmdArgs = line.line().substring(0, line.cursor()).split(" ")
|
||||
val alias = cmdArgs[0]
|
||||
val args = cmdArgs.filterIndexed { i, _ -> i > 0 }
|
||||
if (cmdArgs.size == 1) {
|
||||
candidates.addAll(commands.keys.filter { it.startsWith(alias, ignoreCase = true) }
|
||||
.map { Candidate(it) })
|
||||
} else {
|
||||
val cmd = commands[alias.toLowerCase()]
|
||||
if (cmd != null) {
|
||||
val suggestions = mutableListOf<String>()
|
||||
cmd(suggestions, alias, args.toTypedArray())
|
||||
candidates.addAll(suggestions.map(::Candidate))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
sendMessage("Error completing command: $e")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun runCommand(command: String) {
|
||||
@ -124,7 +202,7 @@ class VIAaaSConsole : SimpleTerminalConsole(), ViaCommandSender {
|
||||
if (runnable == null) {
|
||||
sendMessage("unknown command, try 'help'")
|
||||
} else {
|
||||
runnable(alias, args)
|
||||
runnable(null, alias, args)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
sendMessage("Error running command: $e")
|
||||
@ -160,6 +238,7 @@ object VIAaaSConfig : Config(File("config/viaaas.yml")) {
|
||||
override fun handleConfig(p0: MutableMap<String, Any>?) {
|
||||
}
|
||||
|
||||
val isNativeTransportMc: Boolean get() = this.getBoolean("native-transport-mc", true)
|
||||
val port: Int get() = this.getInt("port", 25565)
|
||||
val bindAddress: String get() = this.getString("bind-address", "localhost")!!
|
||||
val hostName: String get() = this.getString("host-name", "viaaas.localhost")!!
|
||||
@ -171,7 +250,7 @@ class VIAaaSAddress {
|
||||
var realAddress: String? = null
|
||||
var port: Int? = null
|
||||
var online = true
|
||||
var altUsername : String? = null
|
||||
var altUsername: String? = null
|
||||
fun parse(address: String, viaHostName: String): VIAaaSAddress {
|
||||
val parts = address.split('.')
|
||||
var foundDomain = false
|
||||
|
@ -1,7 +1,10 @@
|
||||
# See application.conf in resources for https interface options
|
||||
## CHANGING THIS CONFIG AT RUNTIME ISN'T SUPPORTED
|
||||
## See application.conf in resources for https interface options
|
||||
# Port used for binding Minecraft port
|
||||
port: 25565
|
||||
# Address to bind
|
||||
bind-address: localhost
|
||||
# Host name of this instance, that will be used in the virtual host
|
||||
host-name: viaaas.localhost
|
||||
# Use netty native transport for Minecraft when available.
|
||||
native-transport-mc: true
|
@ -93,9 +93,11 @@
|
||||
|
||||
function storeMcAccount(accessToken, clientToken, name, id) {
|
||||
let accounts = JSON.parse(localStorage.getItem("mc_accounts")) || [];
|
||||
accounts.push({accessToken: accessToken, clientToken: clientToken, name: name, id: id});
|
||||
let account = {accessToken: accessToken, clientToken: clientToken, name: name, id: id};
|
||||
accounts.push(account);
|
||||
localStorage.setItem("mc_accounts", JSON.stringify(accounts));
|
||||
refreshAccountList();
|
||||
return account;
|
||||
}
|
||||
|
||||
function removeMcAccount(id) {
|
||||
@ -149,42 +151,43 @@
|
||||
getMcAccounts().forEach(it => addMcAccountToList(it.id, it.name));
|
||||
}
|
||||
|
||||
function refreshAccountsIfNeeded() {
|
||||
getMcAccounts().forEach(it => {
|
||||
function refreshAccountIfNeeded(it, doneCallback, failCallback) {
|
||||
$.ajax({type: "post",
|
||||
url: localStorage.getItem("cors-proxy") + "https://authserver.mojang.com/validate",
|
||||
data: JSON.stringify({
|
||||
accessToken: it.accessToken,
|
||||
clientToken: it.clientToken
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "json"
|
||||
})
|
||||
.done(() => doneCallback(it))
|
||||
.fail(() => {
|
||||
// Needs refresh
|
||||
console.log("refreshing " + it.id);
|
||||
$.ajax({type: "post",
|
||||
url: localStorage.getItem("cors-proxy") + "https://authserver.mojang.com/validate",
|
||||
url: localStorage.getItem("cors-proxy") + "https://authserver.mojang.com/refresh",
|
||||
data: JSON.stringify({
|
||||
accessToken: it.accessToken,
|
||||
clientToken: it.clientToken
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "json"
|
||||
}).done((data) => {
|
||||
console.log("refreshed " + data.selectedProfile.id);
|
||||
removeMcAccount(data.selectedProfile.id);
|
||||
doneCallback(storeMcAccount(data.accessToken, data.clientToken, data.selectedProfile.name, data.selectedProfile.id));
|
||||
}).fail(() => {
|
||||
// Needs refresh
|
||||
$.ajax({type: "post",
|
||||
url: localStorage.getItem("cors-proxy") + "https://authserver.mojang.com/refresh",
|
||||
data: JSON.stringify({
|
||||
accessToken: it.accessToken,
|
||||
clientToken: it.clientToken
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "json"
|
||||
}).done((data) => {
|
||||
removeMcAccount(data.selectedProfile.id);
|
||||
storeMcAccount(data.accessToken, data.clientToken, data.selectedProfile.name, data.selectedProfile.id);
|
||||
}).fail(() => {
|
||||
if (confirm("failed to refresh token! remove account?")) {
|
||||
removeMcAccount(it.id);
|
||||
}
|
||||
});
|
||||
if (confirm("failed to refresh token! remove account?")) {
|
||||
removeMcAccount(it.id);
|
||||
}
|
||||
failCallback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
refreshAccountList();
|
||||
|
||||
refreshAccountsIfNeeded();
|
||||
|
||||
function listen(token) {
|
||||
socket.send(JSON.stringify({"action": "listen_login_requests", "token": token}));
|
||||
}
|
||||
@ -282,22 +285,28 @@
|
||||
}
|
||||
} else if (parsed.action == "session_hash_request") {
|
||||
if (confirm("Confirm auth request sent from VIAaaS instance? info: " + event.data)) {
|
||||
let accounts = getMcAccounts().filter(it => it.user.toLowerCase() == parsed.user.toLowerCase());
|
||||
let accounts = getMcAccounts().filter(it => it.name.toLowerCase() == parsed.user.toLowerCase());
|
||||
accounts.forEach(it => {
|
||||
$.ajax({type: "post",
|
||||
url: localStorage.getItem("cors-proxy") + "https://sessionserver.mojang.com/session/minecraft/join",
|
||||
data: JSON.stringify({
|
||||
accessToken: it.accessToken,
|
||||
selectedProfile: it.id,
|
||||
serverId: parsed.session_hash
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "json"
|
||||
}).done((data) => {
|
||||
confirmJoin(parsed.session_hash);
|
||||
}).fail((e) => {
|
||||
console.log(e);
|
||||
alert("Failed to authenticate to Minecraft backend server!");
|
||||
refreshAccountIfNeeded(it, (data) => {
|
||||
$.ajax({type: "post",
|
||||
url: localStorage.getItem("cors-proxy") + "https://sessionserver.mojang.com/session/minecraft/join",
|
||||
data: JSON.stringify({
|
||||
accessToken: data.accessToken,
|
||||
selectedProfile: data.id,
|
||||
serverId: parsed.session_hash
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "json"
|
||||
}).done((data) => {
|
||||
confirmJoin(parsed.session_hash);
|
||||
}).fail((e) => {
|
||||
console.log(e);
|
||||
alert("Failed to authenticate to Minecraft backend server!");
|
||||
});
|
||||
}, () => {
|
||||
if (confirm("Couldn't refresh " + parsed.user + " account in browser. Continue without authentication (works on LAN worlds)?")) {
|
||||
confirmJoin(parsed.session_hash);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (accounts.length == 0 && confirm("Couldn't find " + parsed.user + " account in browser. Continue without authentication (works on LAN worlds)?")) {
|
||||
|
Loading…
Reference in New Issue
Block a user