1.19-pre1, add "send access token"

closes #200
This commit is contained in:
creeper123123321 2022-05-22 18:57:25 -03:00
parent fb85cfa329
commit 237761a3d3
13 changed files with 233 additions and 108 deletions

View File

@ -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 }

View File

@ -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!!!");
}
}

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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
}
}

View File

@ -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

View File

@ -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 {

View 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
)

View 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)

View File

@ -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)
}
}

View File

@ -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

View File

@ -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>

View File

@ -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;