mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2025-01-08 19:38:36 +01:00
parent
fb85cfa329
commit
237761a3d3
@ -57,8 +57,8 @@ dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation(kotlin("reflect"))
|
||||
|
||||
val vvVer = "4.3.0-22w19a-SNAPSHOT"
|
||||
val vbVer = "4.3.0-22w19a-SNAPSHOT"
|
||||
val vvVer = "4.3.0-1.19-pre1-SNAPSHOT"
|
||||
val vbVer = "4.3.0-1.19-pre1-SNAPSHOT"
|
||||
val vrVer = "d189537"
|
||||
implementation("com.viaversion:viaversion:$vvVer") { isTransitive = false }
|
||||
implementation("com.viaversion:viabackwards:$vbVer") { isTransitive = false }
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.viaversion.aas.codec;
|
||||
|
||||
import com.viaversion.aas.UtilKt;
|
||||
import com.viaversion.aas.codec.packet.Packet;
|
||||
import com.viaversion.aas.codec.packet.PacketRegistry;
|
||||
import com.viaversion.aas.handler.MinecraftHandler;
|
||||
@ -51,6 +52,7 @@ public class MinecraftCodec extends MessageToMessageCodec<ByteBuf, Packet> {
|
||||
handler.getFrontEnd() ? Direction.SERVERBOUND : Direction.CLIENTBOUND
|
||||
));
|
||||
if (msg.isReadable()) {
|
||||
UtilKt.getMcLogger().debug("Remaining bytes in packet {}", out);
|
||||
throw new StacklessException("Remaining bytes!!!");
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,14 @@ import com.viaversion.aas.codec.packet.Packet;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import com.viaversion.viaversion.api.type.Type;
|
||||
import com.viaversion.viaversion.api.type.types.StringType;
|
||||
import com.viaversion.viaversion.libs.opennbt.tag.builtin.CompoundTag;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LoginStart implements Packet {
|
||||
private String username;
|
||||
private CompoundTag publicKey;
|
||||
private long timestamp;
|
||||
private byte[] key;
|
||||
private byte[] signature;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
@ -25,7 +26,9 @@ public class LoginStart implements Packet {
|
||||
username = new StringType(16).read(byteBuf);
|
||||
if (protocolVersion >= ProtocolVersion.v1_19.getVersion()) {
|
||||
if (byteBuf.readBoolean()) {
|
||||
publicKey = Type.NBT.read(byteBuf);
|
||||
timestamp = byteBuf.readLong();
|
||||
key = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf);
|
||||
signature = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,11 +37,13 @@ public class LoginStart implements Packet {
|
||||
public void encode(@NotNull ByteBuf byteBuf, int protocolVersion) throws Exception {
|
||||
Type.STRING.write(byteBuf, username);
|
||||
if (protocolVersion >= ProtocolVersion.v1_19.getVersion()) {
|
||||
if (publicKey == null) {
|
||||
if (key == null) {
|
||||
byteBuf.writeBoolean(false);
|
||||
} else {
|
||||
byteBuf.writeBoolean(true);
|
||||
Type.NBT.write(byteBuf, publicKey);
|
||||
byteBuf.writeLong(timestamp);
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, key);
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion
|
||||
import de.gerrygames.viarewind.api.ViaRewindConfigImpl
|
||||
import io.ktor.server.application.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.apache.logging.log4j.io.IoBuilder
|
||||
@ -21,7 +21,7 @@ fun main(args: Array<String>) {
|
||||
try {
|
||||
setupSystem()
|
||||
printSplash()
|
||||
CoroutineScope(Dispatchers.IO).launch { viaaasLogger.info(AspirinServer.updaterCheckMessage()) }
|
||||
CoroutineScope(Job()).launch { viaaasLogger.info(AspirinServer.updaterCheckMessage()) }
|
||||
AspirinServer.generateCert()
|
||||
initVia()
|
||||
AspirinServer.listenPorts(args)
|
||||
|
@ -4,7 +4,7 @@ import com.viaversion.aas.AspirinServer
|
||||
import com.viaversion.viaversion.api.command.ViaCommandSender
|
||||
import com.viaversion.viaversion.api.command.ViaSubCommand
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object VIAaaSSubCommand : ViaSubCommand() {
|
||||
@ -12,7 +12,7 @@ object VIAaaSSubCommand : ViaSubCommand() {
|
||||
override fun description(): String = "Info about VIAaaS"
|
||||
override fun execute(p0: ViaCommandSender, p1: Array<out String>): Boolean {
|
||||
p0.sendMessage("VIAaaS version ${AspirinServer.version}")
|
||||
CoroutineScope(Dispatchers.IO).launch { p0.sendMessage(AspirinServer.updaterCheckMessage()) }
|
||||
CoroutineScope(Job()).launch { p0.sendMessage(AspirinServer.updaterCheckMessage()) }
|
||||
return true
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import io.netty.channel.SimpleChannelInboundHandler
|
||||
import io.netty.handler.proxy.ProxyConnectException
|
||||
import io.netty.handler.proxy.ProxyHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import java.net.SocketAddress
|
||||
import java.nio.channels.ClosedChannelException
|
||||
@ -22,7 +22,7 @@ class MinecraftHandler(
|
||||
lateinit var endRemoteAddress: SocketAddress
|
||||
val other: Channel? get() = if (frontEnd) data.backChannel else data.frontChannel
|
||||
var loggedDc = false
|
||||
val coroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
val coroutineScope = CoroutineScope(Job())
|
||||
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, packet: Packet) {
|
||||
if (!ctx.channel().isActive) return
|
||||
|
@ -24,7 +24,7 @@ import io.netty.channel.ChannelOption
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler
|
||||
import io.netty.resolver.NoopAddressResolverGroup
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@ -33,7 +33,7 @@ import java.util.concurrent.TimeUnit
|
||||
object ProtocolDetector {
|
||||
private val loader = CacheLoader.from<InetSocketAddress, CompletableFuture<ProtocolVersion>> { address ->
|
||||
val future = CompletableFuture<ProtocolVersion>()
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
CoroutineScope(Job()).launch {
|
||||
try {
|
||||
val proxyUri = VIAaaSConfig.backendProxy
|
||||
val proxySocket = if (proxyUri == null) null else {
|
||||
|
10
src/main/kotlin/com/viaversion/aas/web/AddressInfo.kt
Normal file
10
src/main/kotlin/com/viaversion/aas/web/AddressInfo.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package com.viaversion.aas.web
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
|
||||
data class AddressInfo(
|
||||
val backVersion: Int,
|
||||
val backHostAndPort: HostAndPort,
|
||||
var frontOnline: Boolean? = null,
|
||||
var backName: String? = null
|
||||
)
|
5
src/main/kotlin/com/viaversion/aas/web/UserInfo.kt
Normal file
5
src/main/kotlin/com/viaversion/aas/web/UserInfo.kt
Normal file
@ -0,0 +1,5 @@
|
||||
package com.viaversion.aas.web
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class UserInfo(val id: UUID, val name: String?, val expiration: Date)
|
@ -8,12 +8,14 @@ import com.viaversion.aas.*
|
||||
import com.viaversion.aas.util.StacklessException
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.websocket.*
|
||||
import kotlinx.coroutines.future.await
|
||||
import java.net.URLEncoder
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@ -35,6 +37,7 @@ class WebLogin : WebState {
|
||||
"unlisten_login_requests" -> handleUnlisten(webClient, obj)
|
||||
"session_hash_response" -> handleSessionResponse(webClient, obj)
|
||||
"parameters_response" -> handleParametersResponse(webClient, obj)
|
||||
"save_access_token" -> handleSaveAccessToken(webClient, obj)
|
||||
else -> throw StacklessException("invalid action!")
|
||||
}
|
||||
|
||||
@ -144,7 +147,7 @@ class WebLogin : WebState {
|
||||
private fun handleParametersResponse(webClient: WebClient, obj: JsonObject) {
|
||||
val callback = UUID.fromString(obj["callback"].asString)
|
||||
webClient.server.addressCallbacks[callback].complete(
|
||||
WebServer.AddressInfo(
|
||||
AddressInfo(
|
||||
backVersion = obj["version"].asString.let {
|
||||
var protocol = Ints.tryParse(it)
|
||||
if (protocol == null) {
|
||||
@ -159,4 +162,13 @@ class WebLogin : WebState {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun handleSaveAccessToken(webClient: WebClient, obj: JsonObject) {
|
||||
val accessToken = obj["mc_access_token"].asString
|
||||
val profile = AspirinServer.httpClient.get("https://api.minecraftservices.com/minecraft/profile") {
|
||||
header("Authorization", "Bearer $accessToken")
|
||||
}.body<JsonObject>()
|
||||
val uuid = parseUndashedId(profile["id"].asString)
|
||||
webClient.server.minecraftAccessTokens.put(uuid, accessToken)
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,14 @@ import com.google.common.cache.CacheBuilder
|
||||
import com.google.common.cache.CacheLoader
|
||||
import com.google.common.collect.MultimapBuilder
|
||||
import com.google.common.collect.Multimaps
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.google.gson.JsonObject
|
||||
import com.viaversion.aas.*
|
||||
import com.viaversion.aas.config.VIAaaSConfig
|
||||
import com.viaversion.aas.util.StacklessException
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.netty.*
|
||||
import io.ktor.server.websocket.*
|
||||
import io.ktor.websocket.*
|
||||
@ -32,12 +33,48 @@ import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
class WebServer {
|
||||
// I don't think i'll need more than 1k/day
|
||||
val clients = ConcurrentHashMap<WebSocketSession, WebClient>()
|
||||
val jwtAlgorithm = Algorithm.HMAC256(VIAaaSConfig.jwtSecret)
|
||||
val coroutineScope = CoroutineScope(Job())
|
||||
|
||||
// Minecraft account -> WebClient
|
||||
val listeners = Multimaps.synchronizedSetMultimap(
|
||||
MultimapBuilder.SetMultimapBuilder
|
||||
.hashKeys()
|
||||
.hashSetValues()
|
||||
.build<UUID, WebClient>()
|
||||
)
|
||||
val usernameToIdCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build<String, CompletableFuture<UUID?>>(CacheLoader.from { name ->
|
||||
coroutineScope.async {
|
||||
AspirinServer.httpClient
|
||||
.get("https://api.mojang.com/users/profiles/minecraft/$name")
|
||||
.body<JsonObject?>()?.get("id")?.asString?.let { parseUndashedId(it) }
|
||||
}.asCompletableFuture()
|
||||
})
|
||||
|
||||
val idToProfileCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build<UUID, CompletableFuture<JsonObject?>>(CacheLoader.from { id ->
|
||||
coroutineScope.async {
|
||||
AspirinServer.httpClient
|
||||
.get("https://sessionserver.mojang.com/session/minecraft/profile/$id")
|
||||
.body<JsonObject?>()
|
||||
}.asCompletableFuture()
|
||||
})
|
||||
|
||||
val sessionHashCallbacks = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build<String, CompletableFuture<Unit>>(CacheLoader.from { _ -> CompletableFuture() })
|
||||
val addressCallbacks = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build<UUID, CompletableFuture<AddressInfo>>(CacheLoader.from { _ -> CompletableFuture() })
|
||||
val minecraftAccessTokens = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
||||
.build<UUID, String>()
|
||||
|
||||
fun generateToken(account: UUID, username: String): String {
|
||||
return JWT.create()
|
||||
@ -61,8 +98,6 @@ class WebServer {
|
||||
return generateToken(info.id, name)
|
||||
}
|
||||
|
||||
data class UserInfo(val id: UUID, val name: String?, val expiration: Date)
|
||||
|
||||
fun parseToken(token: String): UserInfo? {
|
||||
return try {
|
||||
val verified = JWT.require(jwtAlgorithm)
|
||||
@ -79,47 +114,6 @@ class WebServer {
|
||||
}
|
||||
}
|
||||
|
||||
// Minecraft account -> WebClient
|
||||
val listeners = Multimaps.synchronizedSetMultimap(
|
||||
MultimapBuilder.SetMultimapBuilder
|
||||
.hashKeys()
|
||||
.hashSetValues()
|
||||
.build<UUID, WebClient>()
|
||||
)
|
||||
val usernameToIdCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build<String, CompletableFuture<UUID?>>(CacheLoader.from { name ->
|
||||
CoroutineScope(Dispatchers.IO).async {
|
||||
AspirinServer.httpClient
|
||||
.get("https://api.mojang.com/users/profiles/minecraft/$name")
|
||||
.body<JsonObject?>()?.get("id")?.asString?.let { parseUndashedId(it) }
|
||||
}.asCompletableFuture()
|
||||
})
|
||||
|
||||
val idToProfileCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build<UUID, CompletableFuture<JsonObject?>>(CacheLoader.from { id ->
|
||||
CoroutineScope(Dispatchers.IO).async {
|
||||
AspirinServer.httpClient
|
||||
.get("https://sessionserver.mojang.com/session/minecraft/profile/$id")
|
||||
.body<JsonObject?>()
|
||||
}.asCompletableFuture()
|
||||
})
|
||||
|
||||
val sessionHashCallbacks = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build<String, CompletableFuture<Unit>>(CacheLoader.from { _ -> CompletableFuture() })
|
||||
val addressCallbacks = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build<UUID, CompletableFuture<AddressInfo>>(CacheLoader.from { _ -> CompletableFuture() })
|
||||
|
||||
data class AddressInfo(
|
||||
val backVersion: Int,
|
||||
val backHostAndPort: HostAndPort,
|
||||
var frontOnline: Boolean? = null,
|
||||
var backName: String? = null
|
||||
)
|
||||
|
||||
suspend fun requestAddressInfo(frontName: String): CompletableFuture<AddressInfo> {
|
||||
var onlineId: UUID? = null
|
||||
try {
|
||||
@ -131,7 +125,7 @@ class WebServer {
|
||||
|
||||
val callbackId = UUID.randomUUID()
|
||||
val future = addressCallbacks.get(callbackId)
|
||||
CoroutineScope(coroutineContext).apply {
|
||||
coroutineScope.apply {
|
||||
launch(Dispatchers.IO) {
|
||||
run sending@{
|
||||
onlineId?.let {
|
||||
@ -164,16 +158,31 @@ class WebServer {
|
||||
return sent
|
||||
}
|
||||
|
||||
suspend fun joinWithCachedToken(playerId: UUID, hash: String): Boolean {
|
||||
val accessToken = minecraftAccessTokens.getIfPresent(playerId) ?: return false
|
||||
return AspirinServer.httpClient.post("https://sessionserver.mojang.com/session/minecraft/join") {
|
||||
setBody(JsonObject().also {
|
||||
it.addProperty("accessToken", accessToken)
|
||||
it.addProperty("selectedProfile", playerId.toString().replace("-", ""))
|
||||
it.addProperty("serverId", hash)
|
||||
})
|
||||
contentType(ContentType.Application.Json)
|
||||
}.bodyAsText().isEmpty()
|
||||
}
|
||||
|
||||
suspend fun requestSessionJoin(
|
||||
frontName: String,
|
||||
id: UUID, name: String, hash: String,
|
||||
address: SocketAddress, backAddress: SocketAddress
|
||||
): CompletableFuture<Unit> {
|
||||
if (frontName.equals(name, ignoreCase = true) && joinWithCachedToken(id, hash)) {
|
||||
return CompletableFuture.completedFuture(Unit)
|
||||
}
|
||||
val future = sessionHashCallbacks[hash]
|
||||
if (!listeners.containsKey(id)) {
|
||||
future.completeExceptionally(StacklessException("UUID $id ($frontName) isn't listened. Go to web auth."))
|
||||
} else {
|
||||
CoroutineScope(coroutineContext).apply {
|
||||
coroutineScope.apply {
|
||||
launch(Dispatchers.IO) {
|
||||
var info: JsonObject? = null
|
||||
var ptr: String? = null
|
||||
|
@ -92,19 +92,24 @@ frame-src 'self' https://login.microsoftonline.com/ https://login.live.com/"
|
||||
<p>WebSocket connection status: <span class="text-white bg-dark" id="connection_status">?</span></p>
|
||||
<p>CORS Proxy status: <span class="text-white bg-dark" id="cors_status">?</span></p>
|
||||
<hr>
|
||||
<p>Listening to frontend logins from:</p>
|
||||
<p>Listening to front-end logins from:</p>
|
||||
<div id="listening"></div>
|
||||
<div id="actions">
|
||||
<button id="listen_continue" type="button" class="btn btn-primary">Listen to <span id="mcIdUsername"></span>
|
||||
</button>
|
||||
<button id="listen_premium" type="button" class="btn btn-primary">Listen to premium login</button>
|
||||
<button id="listen_offline" type="button" class="btn btn-primary">Listen to offline mode login</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#listenModal"
|
||||
id="listen_open">Listen to logins
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#sendTokenModal"
|
||||
id="send_token_open">Send Access Token
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<p>Connecting to backend server:</p>
|
||||
<div id="connect">
|
||||
<form class="input-group">
|
||||
<input id="connect_address" type="text" class="form-control" placeholder="Address:Port" aria-label="Address and Port">
|
||||
<form class="input-group mb-3">
|
||||
<input id="connect_address" type="text" class="form-control" placeholder="Address:Port"
|
||||
aria-label="Address and Port">
|
||||
<input id="connect_version" type="text" class="form-control" placeholder="Version" aria-label="Version">
|
||||
<select class="form-select" id="connect_online" aria-label="Online Mode">
|
||||
<option value="null" selected>Front Online Mode...</option>
|
||||
@ -112,11 +117,12 @@ frame-src 'self' https://login.microsoftonline.com/ https://login.live.com/"
|
||||
<option value="true">Force online</option>
|
||||
<option value="false">Force offline (recommended for Geyser)</option>
|
||||
</select>
|
||||
<input id="connect_user" type="text" class="form-control" placeholder="Back Username" aria-label="Backend Username">
|
||||
<input id="connect_user" type="text" class="form-control" placeholder="Back Username"
|
||||
aria-label="Backend Username">
|
||||
</form>
|
||||
<p>You can also use <a href="https://jo0001.github.io/ViaSetup/aspirin" id="viasetup_address">the address
|
||||
generator</a> to specify the server</p>
|
||||
</div>
|
||||
<p>You can also use <a href="https://jo0001.github.io/ViaSetup/aspirin" id="viasetup_address">address
|
||||
generator</a></p>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="settings-tab" class="tab-pane fade" id="settings">
|
||||
@ -256,6 +262,67 @@ frame-src 'self' https://login.microsoftonline.com/ https://login.live.com/"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-hidden="true" aria-labelledby="listenModalLabel" class="modal fade" id="listenModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<form id="form_listen">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="listenModalLabel">Listen to logins</h5>
|
||||
<button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>The instance will send impersonation requests to this page. Please note
|
||||
that they're verified by UUID, so offline and online accounts
|
||||
are considered different.</p>
|
||||
<p>VIAaaS will also request the address parameters when they're not specified.
|
||||
They're verified by username. Online-mode listeners have priority.</p>
|
||||
<div class="mb-3">
|
||||
<label for="listen_username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="listen_username">
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="listen_online" checked>
|
||||
<label for="listen_online" class="form-check-label">Online Mode</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal">Listen</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-hidden="true" aria-labelledby="listenModalLabel" class="modal fade" id="sendTokenModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<form id="form_send_token">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="sendTokenModalLabel">Save Access Token in Instance</h5>
|
||||
<button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>The instance will cache your Minecraft access token, allowing you to connect without keeping
|
||||
this page open.</p>
|
||||
<p>Note that the access token expires in ~1 day and a compromised instance may access your account
|
||||
with malicious intents.</p>
|
||||
<p>You'll need to connect as online-mode in the front-end. It's not possible to use the cached
|
||||
token when changing the backend username</p>
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="send_token_user">Account</label>
|
||||
<select class="form-select" id="send_token_user">
|
||||
<option selected>Choose...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="modal">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast-container position-absolute top-0 end-0 p-3" id="toasts"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,7 +2,7 @@
|
||||
let urlParams = new URLSearchParams();
|
||||
window.location.hash.substring(1).split("?")
|
||||
.map(it => new URLSearchParams(it)
|
||||
.forEach((a, b) => urlParams.append(b, a)));
|
||||
.forEach((a, b) => urlParams.append(b, a)));
|
||||
let mcIdUsername = urlParams.get("username");
|
||||
let mcauth_code = urlParams.get("mcauth_code");
|
||||
let mcauth_success = urlParams.get("mcauth_success");
|
||||
@ -53,6 +53,10 @@ $(() => {
|
||||
$("#form_add_ms").on("submit", () => loginMs());
|
||||
$("#form_ws_url").on("submit", () => setWsUrl($("#ws-url").val()));
|
||||
$("#form_cors_proxy").on("submit", () => setCorsProxy($("#cors-proxy").val()));
|
||||
$("#form_listen").on("submit", () => submittedListen());
|
||||
$("#form_send_token").on("submit", () => submittedSendToken());
|
||||
$("#en_notific").on("click", () => Notification.requestPermission().then(renderActions));
|
||||
$("#listen_continue").on("click", () => clickedListenContinue());
|
||||
|
||||
ohNo();
|
||||
|
||||
@ -101,40 +105,54 @@ function addMcAccountToList(account) {
|
||||
$(accounts).append(line);
|
||||
}
|
||||
|
||||
function addListSendToken(username) {
|
||||
let line = $("<option class='mc_username'></option>");
|
||||
line.text(username);
|
||||
$("#send_token_user").append(line);
|
||||
}
|
||||
|
||||
function refreshAccountList() {
|
||||
accounts.innerHTML = "";
|
||||
$("#send_token_user .mc_username").remove();
|
||||
getActiveAccounts()
|
||||
.filter(it => it instanceof MojangAccount)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.forEach(it => addMcAccountToList(it));
|
||||
getMicrosoftUsers()
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.forEach(username => {
|
||||
let mcAcc = findAccountByMs(username);
|
||||
if (!mcAcc) return;
|
||||
addMcAccountToList(mcAcc);
|
||||
.forEach(it => {
|
||||
addMcAccountToList(it)
|
||||
addListSendToken(it.name)
|
||||
});
|
||||
}
|
||||
|
||||
$("#en_notific").on("click", () => Notification.requestPermission().then(renderActions));
|
||||
$("#listen_premium").on("click", () => {
|
||||
let user = prompt("Premium username (case-sensitive): ", "");
|
||||
if (!user) return;
|
||||
let callbackUrl = new URL(location);
|
||||
callbackUrl.search = "";
|
||||
callbackUrl.hash = "#username=" + encodeURIComponent(user);
|
||||
location.href = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||
+ "?callback=" + encodeURIComponent(callbackUrl.toString());
|
||||
});
|
||||
$("#listen_offline").on("click", () => {
|
||||
let user = prompt("Offline username (case-sensitive): ", "");
|
||||
if (!user) return;
|
||||
let taskId = Math.random();
|
||||
workers.forEach(it => it.postMessage({action: "listen_pow", user: user, id: taskId, deltaTime: deltaTime}));
|
||||
addToast("Offline username", "Please wait a minute...");
|
||||
});
|
||||
$("#mcIdUsername").text(mcIdUsername);
|
||||
$("#listen_continue").on("click", () => {
|
||||
|
||||
function submittedListen() {
|
||||
let user = $("#listen_username").val();
|
||||
if (!user) return;
|
||||
if ($("#listen_online")[0].checked) {
|
||||
let callbackUrl = new URL(location);
|
||||
callbackUrl.search = "";
|
||||
callbackUrl.hash = "#username=" + encodeURIComponent(user);
|
||||
location.href = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||
+ "?callback=" + encodeURIComponent(callbackUrl.toString());
|
||||
} else {
|
||||
let taskId = Math.random();
|
||||
workers.forEach(it => it.postMessage({action: "listen_pow", user: user, id: taskId, deltaTime: deltaTime}));
|
||||
addToast("Offline username", "Please wait a minute...");
|
||||
}
|
||||
}
|
||||
|
||||
function submittedSendToken() {
|
||||
findAccountByMcName($("#send_token_user").val())
|
||||
.acquireActiveToken()
|
||||
.then(acc => {
|
||||
sendSocket(JSON.stringify({
|
||||
"action": "save_access_token",
|
||||
"mc_access_token": acc.accessToken
|
||||
}))
|
||||
})
|
||||
.catch(e => addToast("Failed to send access token", e));
|
||||
}
|
||||
|
||||
function clickedListenContinue() {
|
||||
sendSocket(JSON.stringify({
|
||||
"action": "minecraft_id_login",
|
||||
"username": mcIdUsername,
|
||||
@ -142,13 +160,13 @@ $("#listen_continue").on("click", () => {
|
||||
}));
|
||||
mcauth_code = null;
|
||||
renderActions();
|
||||
});
|
||||
}
|
||||
|
||||
function renderActions() {
|
||||
$("#en_notific").hide();
|
||||
$("#listen_continue").hide();
|
||||
$("#listen_premium").hide();
|
||||
$("#listen_offline").hide();
|
||||
$("#listen_open").hide();
|
||||
$("#send_token_open").hide();
|
||||
|
||||
if (Notification.permission === "default") {
|
||||
$("#en_notific").show();
|
||||
@ -157,8 +175,8 @@ function renderActions() {
|
||||
if (mcIdUsername != null && mcauth_code != null) {
|
||||
$("#listen_continue").show();
|
||||
}
|
||||
$("#listen_premium").show();
|
||||
$("#listen_offline").show();
|
||||
$("#listen_open").show();
|
||||
$("#send_token_open").show();
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,10 +376,6 @@ function getActiveAccounts() {
|
||||
return activeAccounts;
|
||||
}
|
||||
|
||||
function getMicrosoftUsers() {
|
||||
return (myMSALObj.getAllAccounts() || []).map(it => it.username);
|
||||
}
|
||||
|
||||
class McAccount {
|
||||
constructor(id, username, accessToken) {
|
||||
this.id = id;
|
||||
@ -589,6 +603,7 @@ function loginMc(user, pass) {
|
||||
function getLoginRequest() {
|
||||
return {scopes: ["XboxLive.signin"]};
|
||||
}
|
||||
|
||||
let redirectUrl = "https://viaversion.github.io/VIAaaS/src/main/resources/web/";
|
||||
if (location.hostname === "localhost" || whitelistedOrigin.includes(location.origin)) {
|
||||
redirectUrl = location.origin + location.pathname;
|
||||
|
Loading…
Reference in New Issue
Block a user