mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 19:35:00 -04:00
Microsoft accounts
This commit is contained in:
parent
ccccc9cefe
commit
9314b3acb4
6
pom.xml
6
pom.xml
@ -122,6 +122,12 @@
|
|||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-web</artifactId>
|
||||||
|
<version>${javafx.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.jfoenix</groupId>
|
<groupId>com.jfoenix</groupId>
|
||||||
<artifactId>jfoenix</artifactId>
|
<artifactId>jfoenix</artifactId>
|
||||||
|
@ -61,6 +61,7 @@ public final class Minosoft {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
MinosoftCommandLineArguments.parseCommandLineArguments(args);
|
MinosoftCommandLineArguments.parseCommandLineArguments(args);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown(ShutdownReasons.UNKNOWN), "ShutdownHook"));
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown(ShutdownReasons.UNKNOWN), "ShutdownHook"));
|
||||||
|
Util.initUtilClasses();
|
||||||
|
|
||||||
Log.info("Starting...");
|
Log.info("Starting...");
|
||||||
AsyncTaskWorker taskWorker = new AsyncTaskWorker("StartUp");
|
AsyncTaskWorker taskWorker = new AsyncTaskWorker("StartUp");
|
||||||
@ -245,4 +246,5 @@ public final class Minosoft {
|
|||||||
public static CountUpAndDownLatch getStartStatusLatch() {
|
public static CountUpAndDownLatch getStartStatusLatch() {
|
||||||
return START_STATUS_LATCH;
|
return START_STATUS_LATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package de.bixilon.minosoft.config;
|
|||||||
import com.google.common.collect.HashBiMap;
|
import com.google.common.collect.HashBiMap;
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import de.bixilon.minosoft.data.accounts.Account;
|
import de.bixilon.minosoft.data.accounts.Account;
|
||||||
|
import de.bixilon.minosoft.data.accounts.MicrosoftAccount;
|
||||||
import de.bixilon.minosoft.data.accounts.MojangAccount;
|
import de.bixilon.minosoft.data.accounts.MojangAccount;
|
||||||
import de.bixilon.minosoft.data.accounts.OfflineAccount;
|
import de.bixilon.minosoft.data.accounts.OfflineAccount;
|
||||||
import de.bixilon.minosoft.gui.main.Server;
|
import de.bixilon.minosoft.gui.main.Server;
|
||||||
@ -77,6 +78,7 @@ public class Configuration {
|
|||||||
Account account = switch (data.get("type").getAsString()) {
|
Account account = switch (data.get("type").getAsString()) {
|
||||||
case "mojang" -> MojangAccount.deserialize(data);
|
case "mojang" -> MojangAccount.deserialize(data);
|
||||||
case "offline" -> OfflineAccount.deserialize(data);
|
case "offline" -> OfflineAccount.deserialize(data);
|
||||||
|
case "microsoft" -> MicrosoftAccount.deserialize(data);
|
||||||
default -> throw new IllegalArgumentException("Unexpected value: " + data.get("type").getAsString());
|
default -> throw new IllegalArgumentException("Unexpected value: " + data.get("type").getAsString());
|
||||||
};
|
};
|
||||||
this.accountList.put(account.getId(), account);
|
this.accountList.put(account.getId(), account);
|
||||||
@ -105,16 +107,16 @@ public class Configuration {
|
|||||||
JsonObject jsonObject = DEFAULT_CONFIGURATION.deepCopy();
|
JsonObject jsonObject = DEFAULT_CONFIGURATION.deepCopy();
|
||||||
synchronized (this.config) {
|
synchronized (this.config) {
|
||||||
|
|
||||||
// accounts
|
// servers
|
||||||
JsonObject accountsEntriesJson = jsonObject.getAsJsonObject("servers").getAsJsonObject("entries");
|
JsonObject serversEntriesJson = jsonObject.getAsJsonObject("servers").getAsJsonObject("entries");
|
||||||
for (Map.Entry<Integer, Server> entry : this.serverList.entrySet()) {
|
for (Map.Entry<Integer, Server> entry : this.serverList.entrySet()) {
|
||||||
accountsEntriesJson.add(String.valueOf(entry.getKey()), entry.getValue().serialize());
|
serversEntriesJson.add(String.valueOf(entry.getKey()), entry.getValue().serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// servers
|
// accounts
|
||||||
JsonObject serversEntriesJson = jsonObject.getAsJsonObject("accounts").getAsJsonObject("entries");
|
JsonObject accountsEntriesJson = jsonObject.getAsJsonObject("accounts").getAsJsonObject("entries");
|
||||||
for (Map.Entry<String, Account> entry : this.accountList.entrySet()) {
|
for (Map.Entry<String, Account> entry : this.accountList.entrySet()) {
|
||||||
serversEntriesJson.add(entry.getKey(), entry.getValue().serialize());
|
accountsEntriesJson.add(entry.getKey(), entry.getValue().serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// rest of data
|
// rest of data
|
||||||
|
@ -15,20 +15,34 @@ package de.bixilon.minosoft.data.accounts;
|
|||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import de.bixilon.minosoft.Minosoft;
|
import de.bixilon.minosoft.Minosoft;
|
||||||
|
import de.bixilon.minosoft.gui.main.cells.AccountListCell;
|
||||||
|
import de.bixilon.minosoft.util.logging.Log;
|
||||||
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
|
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
|
||||||
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
|
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class Account {
|
public abstract class Account {
|
||||||
private final String username;
|
protected final String username;
|
||||||
private final UUID uuid;
|
protected final UUID uuid;
|
||||||
|
|
||||||
protected Account(String username, UUID uuid) {
|
protected Account(String username, UUID uuid) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addAccount(Account account) {
|
||||||
|
Minosoft.getConfig().putAccount(account);
|
||||||
|
account.saveToConfig();
|
||||||
|
Log.info(String.format("Added and saved account (type=%s, id=%s, username=%s, uuid=%s)", account.getClass().getSimpleName(), account.getId(), account.getUsername(), account.getUUID()));
|
||||||
|
Platform.runLater(() -> AccountListCell.ACCOUNT_LIST_VIEW.getItems().add(account));
|
||||||
|
if (Minosoft.getConfig().getSelectedAccount() == null) {
|
||||||
|
// select account
|
||||||
|
Minosoft.selectAccount(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return this.username;
|
return this.username;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Minosoft
|
||||||
|
* Copyright (C) 2020 Moritz Zwerger
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.bixilon.minosoft.data.accounts;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import de.bixilon.minosoft.util.Util;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class MicrosoftAccount extends MojangAccount {
|
||||||
|
|
||||||
|
public MicrosoftAccount(String accessToken, String id, UUID uuid, String username) {
|
||||||
|
super(accessToken, id, uuid, username, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MicrosoftAccount deserialize(JsonObject json) {
|
||||||
|
return new MicrosoftAccount(json.get("accessToken").getAsString(), json.get("id").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString()), json.get("username").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject serialize() {
|
||||||
|
JsonObject json = super.serialize();
|
||||||
|
json.addProperty("type", "microsoft");
|
||||||
|
json.remove("email");
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
@ -24,11 +24,11 @@ import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionExcepti
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class MojangAccount extends Account {
|
public class MojangAccount extends Account {
|
||||||
private final String id;
|
protected final String id;
|
||||||
private final String email;
|
protected final String email;
|
||||||
private String accessToken;
|
protected String accessToken;
|
||||||
private RefreshStates lastRefreshStatus;
|
protected RefreshStates lastRefreshStatus;
|
||||||
private boolean needsRefresh = true;
|
protected boolean needsRefresh = true;
|
||||||
|
|
||||||
public MojangAccount(String username, JsonObject json) {
|
public MojangAccount(String username, JsonObject json) {
|
||||||
super(json.getAsJsonObject("selectedProfile").get("name").getAsString(), Util.getUUIDFromString(json.getAsJsonObject("selectedProfile").get("id").getAsString()));
|
super(json.getAsJsonObject("selectedProfile").get("name").getAsString(), Util.getUUIDFromString(json.getAsJsonObject("selectedProfile").get("id").getAsString()));
|
||||||
|
@ -53,6 +53,7 @@ public enum Strings {
|
|||||||
SERVER_ACTION_DELETE,
|
SERVER_ACTION_DELETE,
|
||||||
|
|
||||||
SESSIONS_ACTION_DISCONNECT,
|
SESSIONS_ACTION_DISCONNECT,
|
||||||
|
ACCOUNT_MODAL_MENU_ADD_MICROSOFT_ACCOUNT,
|
||||||
ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT,
|
ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT,
|
||||||
ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT,
|
ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT,
|
||||||
MAIN_WINDOW_TITLE,
|
MAIN_WINDOW_TITLE,
|
||||||
@ -89,6 +90,8 @@ public enum Strings {
|
|||||||
LOGIN_OFFLINE_UUID,
|
LOGIN_OFFLINE_UUID,
|
||||||
LOGIN_OFFLINE_ADD_BUTTON,
|
LOGIN_OFFLINE_ADD_BUTTON,
|
||||||
|
|
||||||
|
LOGIN_MICROSOFT_DIALOG_TITLE,
|
||||||
|
|
||||||
MINOSOFT_STILL_STARTING_TITLE,
|
MINOSOFT_STILL_STARTING_TITLE,
|
||||||
MINOSOFT_STILL_STARTING_HEADER,
|
MINOSOFT_STILL_STARTING_HEADER,
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ public class AccountWindow implements Initializable {
|
|||||||
public BorderPane accountPane;
|
public BorderPane accountPane;
|
||||||
public MenuItem menuAddMojangAccount;
|
public MenuItem menuAddMojangAccount;
|
||||||
public MenuItem menuAddOfflineAccount;
|
public MenuItem menuAddOfflineAccount;
|
||||||
|
public MenuItem menuAddMicrosoftAccount;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
@ -43,13 +44,23 @@ public class AccountWindow implements Initializable {
|
|||||||
AccountListCell.ACCOUNT_LIST_VIEW.setItems(accounts);
|
AccountListCell.ACCOUNT_LIST_VIEW.setItems(accounts);
|
||||||
this.accountPane.setCenter(AccountListCell.ACCOUNT_LIST_VIEW);
|
this.accountPane.setCenter(AccountListCell.ACCOUNT_LIST_VIEW);
|
||||||
|
|
||||||
|
this.menuAddMicrosoftAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_MICROSOFT_ACCOUNT));
|
||||||
this.menuAddMojangAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT));
|
this.menuAddMojangAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT));
|
||||||
this.menuAddOfflineAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT));
|
this.menuAddOfflineAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addMicrosoftAccount() {
|
||||||
|
try {
|
||||||
|
GUITools.showPane("/layout/dialogs/login/microsoft.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_MICROSOFT_DIALOG_TITLE));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Minosoft.shutdown(e.getMessage(), ShutdownReasons.LAUNCHER_FXML_LOAD_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addMojangAccount() {
|
public void addMojangAccount() {
|
||||||
try {
|
try {
|
||||||
GUITools.showPane("/layout/dialogs/login_mojang.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_MOJANG_DIALOG_TITLE));
|
GUITools.showPane("/layout/dialogs/login/mojang.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_MOJANG_DIALOG_TITLE));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Minosoft.shutdown(e.getMessage(), ShutdownReasons.LAUNCHER_FXML_LOAD_ERROR);
|
Minosoft.shutdown(e.getMessage(), ShutdownReasons.LAUNCHER_FXML_LOAD_ERROR);
|
||||||
@ -58,7 +69,7 @@ public class AccountWindow implements Initializable {
|
|||||||
|
|
||||||
public void addOfflineAccount() {
|
public void addOfflineAccount() {
|
||||||
try {
|
try {
|
||||||
GUITools.showPane("/layout/dialogs/login_offline.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_OFFLINE_DIALOG_TITLE));
|
GUITools.showPane("/layout/dialogs/login/offline.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_OFFLINE_DIALOG_TITLE));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Minosoft.shutdown(e.getMessage(), ShutdownReasons.LAUNCHER_FXML_LOAD_ERROR);
|
Minosoft.shutdown(e.getMessage(), ShutdownReasons.LAUNCHER_FXML_LOAD_ERROR);
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Minosoft
|
||||||
|
* Copyright (C) 2020 Moritz Zwerger
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.bixilon.minosoft.gui.main.dialogs.login;
|
||||||
|
|
||||||
|
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||||
|
import javafx.concurrent.Worker;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.web.WebView;
|
||||||
|
|
||||||
|
import java.net.CookieHandler;
|
||||||
|
import java.net.CookieManager;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class MicrosoftLoginController implements Initializable {
|
||||||
|
public HBox hBox;
|
||||||
|
public WebView webView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
|
CookieHandler.setDefault(new CookieManager());
|
||||||
|
|
||||||
|
this.webView.getEngine().setJavaScriptEnabled(true);
|
||||||
|
this.webView.setContextMenuEnabled(false);
|
||||||
|
this.webView.getEngine().loadContent("Loading...");
|
||||||
|
this.webView.getEngine().getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue == Worker.State.SUCCEEDED) {
|
||||||
|
if (this.webView.getEngine().getLocation().startsWith("ms-xal-" + ProtocolDefinition.MICROSOFT_ACCOUNT_APPLICATION_ID)) {
|
||||||
|
// login is being handled by MicrosoftOAuthUtils. We can go now...
|
||||||
|
this.hBox.getScene().getWindow().hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
requestOathFlowToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestOathFlowToken() {
|
||||||
|
this.webView.getEngine().load(ProtocolDefinition.MICROSOFT_ACCOUNT_OAUTH_FLOW_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void login(ActionEvent event) {
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
}
|
@ -11,17 +11,15 @@
|
|||||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.bixilon.minosoft.gui.main.dialogs;
|
package de.bixilon.minosoft.gui.main.dialogs.login;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXPasswordField;
|
import com.jfoenix.controls.JFXPasswordField;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
||||||
import de.bixilon.minosoft.Minosoft;
|
import de.bixilon.minosoft.data.accounts.Account;
|
||||||
import de.bixilon.minosoft.data.accounts.MojangAccount;
|
import de.bixilon.minosoft.data.accounts.MojangAccount;
|
||||||
import de.bixilon.minosoft.data.locale.LocaleManager;
|
import de.bixilon.minosoft.data.locale.LocaleManager;
|
||||||
import de.bixilon.minosoft.data.locale.Strings;
|
import de.bixilon.minosoft.data.locale.Strings;
|
||||||
import de.bixilon.minosoft.gui.main.cells.AccountListCell;
|
|
||||||
import de.bixilon.minosoft.util.logging.Log;
|
|
||||||
import de.bixilon.minosoft.util.mojang.api.MojangAuthentication;
|
import de.bixilon.minosoft.util.mojang.api.MojangAuthentication;
|
||||||
import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException;
|
import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException;
|
||||||
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
|
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
|
||||||
@ -85,19 +83,10 @@ public class MojangLoginController implements Initializable {
|
|||||||
new Thread(() -> { // ToDo: recycle thread
|
new Thread(() -> { // ToDo: recycle thread
|
||||||
try {
|
try {
|
||||||
MojangAccount account = MojangAuthentication.login(this.email.getText(), this.password.getText());
|
MojangAccount account = MojangAuthentication.login(this.email.getText(), this.password.getText());
|
||||||
|
Account.addAccount(account);
|
||||||
|
|
||||||
|
Platform.runLater(this::close);
|
||||||
|
|
||||||
account.setNeedRefresh(false);
|
|
||||||
Minosoft.getConfig().putAccount(account);
|
|
||||||
account.saveToConfig();
|
|
||||||
Log.info(String.format("Added and saved account (type=mojang, username=%s, email=%s, uuid=%s)", account.getUsername(), account.getEmail(), account.getUUID()));
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
AccountListCell.ACCOUNT_LIST_VIEW.getItems().add(account);
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
if (Minosoft.getConfig().getSelectedAccount() == null) {
|
|
||||||
// select account
|
|
||||||
Minosoft.selectAccount(account);
|
|
||||||
}
|
|
||||||
} catch (AuthenticationException | NoNetworkConnectionException e) {
|
} catch (AuthenticationException | NoNetworkConnectionException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
@ -11,7 +11,7 @@
|
|||||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.bixilon.minosoft.gui.main.dialogs;
|
package de.bixilon.minosoft.gui.main.dialogs.login;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
@ -13,12 +13,12 @@
|
|||||||
|
|
||||||
package de.bixilon.minosoft.protocol.protocol;
|
package de.bixilon.minosoft.protocol.protocol;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import de.bixilon.minosoft.data.inventory.Slot;
|
import de.bixilon.minosoft.data.inventory.Slot;
|
||||||
import de.bixilon.minosoft.data.text.ChatComponent;
|
import de.bixilon.minosoft.data.text.ChatComponent;
|
||||||
import de.bixilon.minosoft.data.world.BlockPosition;
|
import de.bixilon.minosoft.data.world.BlockPosition;
|
||||||
import de.bixilon.minosoft.protocol.network.Connection;
|
import de.bixilon.minosoft.protocol.network.Connection;
|
||||||
|
import de.bixilon.minosoft.util.Util;
|
||||||
import de.bixilon.minosoft.util.nbt.tag.CompoundTag;
|
import de.bixilon.minosoft.util.nbt.tag.CompoundTag;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -82,7 +82,7 @@ public class OutByteBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeJSON(JsonObject json) {
|
public void writeJSON(JsonObject json) {
|
||||||
writeString(new Gson().toJson(json));
|
writeString(Util.GSON.toJson(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeString(String string) {
|
public void writeString(String string) {
|
||||||
|
@ -71,6 +71,15 @@ public final class ProtocolDefinition {
|
|||||||
public static final String MOJANG_URL_JOIN = "https://sessionserver.mojang.com/session/minecraft/join";
|
public static final String MOJANG_URL_JOIN = "https://sessionserver.mojang.com/session/minecraft/join";
|
||||||
public static final String MOJANG_URL_REFRESH = "https://authserver.mojang.com/refresh";
|
public static final String MOJANG_URL_REFRESH = "https://authserver.mojang.com/refresh";
|
||||||
|
|
||||||
|
public static final String MICROSOFT_ACCOUNT_APPLICATION_ID = "00000000402b5328"; // ToDo: Should we use our own application id?
|
||||||
|
// public static final String MICROSOFT_ACCOUNT_APPLICATION_ID = "fe6f0fbf-3038-486a-9c84-6a28b71e0455";
|
||||||
|
public static final String MICROSOFT_ACCOUNT_OAUTH_FLOW_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=" + MICROSOFT_ACCOUNT_APPLICATION_ID + "&scope=XboxLive.signin%20offline_access&response_type=code";
|
||||||
|
public static final String MICROSOFT_ACCOUNT_AUTH_TOKEN_URL = "https://login.live.com/oauth20_token.srf";
|
||||||
|
public static final String MICROSOFT_ACCOUNT_XBOX_LIVE_AUTHENTICATE_URL = "https://user.auth.xboxlive.com/user/authenticate";
|
||||||
|
public static final String MICROSOFT_ACCOUNT_XSTS_URL = "https://xsts.auth.xboxlive.com/xsts/authorize";
|
||||||
|
public static final String MICROSOFT_ACCOUNT_MINECRAFT_LOGIN_WITH_XBOX_URL = "https://api.minecraftservices.com/authentication/login_with_xbox";
|
||||||
|
public static final String MICROSOFT_ACCOUNT_GET_MOJANG_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile";
|
||||||
|
|
||||||
public static final char[] OBFUSCATED_CHARS = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".toCharArray();
|
public static final char[] OBFUSCATED_CHARS = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".toCharArray();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -22,26 +22,64 @@ import java.net.URI;
|
|||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
public final class HTTP {
|
public final class HTTP {
|
||||||
|
|
||||||
public static HttpResponse<String> postJson(String url, JsonObject json) throws IOException, InterruptedException {
|
public static HttpResponse<String> postJson(String url, String json, HashMap<String, String> headers) throws IOException, InterruptedException {
|
||||||
|
headers.put("Content-Type", "application/json");
|
||||||
|
headers.put("Accept", "application/json");
|
||||||
|
|
||||||
HttpClient client = HttpClient.newHttpClient();
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).POST(HttpRequest.BodyPublishers.ofString(json.toString())).header("Content-Type", "application/json").build();
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(json))
|
||||||
|
.headers(Util.headersMapToArray(headers))
|
||||||
|
.build();
|
||||||
|
return client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpResponse<String> postJson(String url, JsonObject json) throws IOException, InterruptedException {
|
||||||
|
return postJson(url, Util.GSON.toJson(json), new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpResponse<String> postJson(String url, String json) throws IOException, InterruptedException {
|
||||||
|
return postJson(url, json, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpResponse<String> get(String url, HashMap<String, String> headers) throws IOException, InterruptedException {
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.headers(Util.headersMapToArray(headers))
|
||||||
|
.build();
|
||||||
return client.send(request, HttpResponse.BodyHandlers.ofString());
|
return client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HttpResponse<String> get(String url) throws IOException, InterruptedException {
|
public static HttpResponse<String> get(String url) throws IOException, InterruptedException {
|
||||||
HttpClient client = HttpClient.newHttpClient();
|
return get(url, new HashMap<>());
|
||||||
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
|
|
||||||
return client.send(request, HttpResponse.BodyHandlers.ofString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonElement getJson(String url) throws IOException, InterruptedException {
|
|
||||||
HttpResponse<String> response = get(url);
|
public static JsonElement getJson(String url, HashMap<String, String> headers) throws IOException, InterruptedException {
|
||||||
|
HttpResponse<String> response = get(url, headers);
|
||||||
if (response.statusCode() != 200) {
|
if (response.statusCode() != 200) {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
return JsonParser.parseString(response.body());
|
return JsonParser.parseString(response.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JsonElement getJson(String url) throws IOException, InterruptedException {
|
||||||
|
return getJson(url, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpResponse<String> postData(String url, HashMap<String, String> data) throws IOException, InterruptedException {
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(Util.mapToUrlQuery(data)))
|
||||||
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.build();
|
||||||
|
return client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,24 +14,26 @@
|
|||||||
package de.bixilon.minosoft.util;
|
package de.bixilon.minosoft.util;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import de.bixilon.minosoft.protocol.network.Connection;
|
import de.bixilon.minosoft.protocol.network.Connection;
|
||||||
import de.bixilon.minosoft.protocol.protocol.InByteBuffer;
|
import de.bixilon.minosoft.protocol.protocol.InByteBuffer;
|
||||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||||
|
import de.bixilon.minosoft.util.logging.Log;
|
||||||
|
import de.bixilon.minosoft.util.microsoft.MicrosoftOAuthUtils;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -46,6 +48,8 @@ public final class Util {
|
|||||||
private static final Field JSON_READER_POS_FIELD;
|
private static final Field JSON_READER_POS_FIELD;
|
||||||
private static final Field JSON_READER_LINE_START_FIELD;
|
private static final Field JSON_READER_LINE_START_FIELD;
|
||||||
|
|
||||||
|
public static final Gson GSON = new Gson();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
new JsonReader(new StringReader(""));
|
new JsonReader(new StringReader(""));
|
||||||
Class<?> jsonReadClass = JsonReader.class;
|
Class<?> jsonReadClass = JsonReader.class;
|
||||||
@ -324,4 +328,48 @@ public final class Util {
|
|||||||
throw new IllegalArgumentException("Not a valid url:" + url);
|
throw new IllegalArgumentException("Not a valid url:" + url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> void forceClassInit(Class<T> clazz) {
|
||||||
|
try {
|
||||||
|
Class.forName(clazz.getName(), true, clazz.getClassLoader());
|
||||||
|
} catch (ClassNotFoundException exception) {
|
||||||
|
throw new RuntimeException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initUtilClasses() {
|
||||||
|
forceClassInit(Log.class);
|
||||||
|
forceClassInit(MicrosoftOAuthUtils.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> urlQueryToMap(String query) {
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
for (String parameter : query.split("&")) {
|
||||||
|
String[] split = parameter.split("=");
|
||||||
|
map.put(split[0], split[1]);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String mapToUrlQuery(Map<String, String> data) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (Map.Entry<String, String> entry : data.entrySet()) {
|
||||||
|
if (!builder.isEmpty()) {
|
||||||
|
builder.append("&");
|
||||||
|
}
|
||||||
|
builder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
|
||||||
|
builder.append("=");
|
||||||
|
builder.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] headersMapToArray(Map<String, String> headers) {
|
||||||
|
List<String> headerList = new ArrayList<>();
|
||||||
|
for (var entry : headers.entrySet()) {
|
||||||
|
headerList.add(entry.getKey());
|
||||||
|
headerList.add(entry.getValue());
|
||||||
|
}
|
||||||
|
return headerList.toArray(new String[]{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package de.bixilon.minosoft.util.microsoft
|
||||||
|
|
||||||
|
class LoginException(val errorCode: Int, override val message: String, val errorMessage: String) : Exception()
|
@ -0,0 +1,173 @@
|
|||||||
|
package de.bixilon.minosoft.util.microsoft
|
||||||
|
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import com.jfoenix.controls.JFXAlert
|
||||||
|
import com.jfoenix.controls.JFXDialogLayout
|
||||||
|
import de.bixilon.minosoft.config.StaticConfiguration
|
||||||
|
import de.bixilon.minosoft.data.accounts.Account
|
||||||
|
import de.bixilon.minosoft.data.accounts.MicrosoftAccount
|
||||||
|
import de.bixilon.minosoft.gui.main.GUITools
|
||||||
|
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||||
|
import de.bixilon.minosoft.util.HTTP
|
||||||
|
import de.bixilon.minosoft.util.Util
|
||||||
|
import de.bixilon.minosoft.util.logging.Log
|
||||||
|
import javafx.application.Platform
|
||||||
|
import javafx.scene.control.TextArea
|
||||||
|
import javafx.scene.text.Text
|
||||||
|
import javafx.stage.Stage
|
||||||
|
import java.net.URL
|
||||||
|
import java.net.URLConnection
|
||||||
|
import java.net.URLStreamHandler
|
||||||
|
|
||||||
|
object MicrosoftOAuthUtils {
|
||||||
|
val NULL_URL_CONNECTION: URLConnection = object : URLConnection(null) {
|
||||||
|
override fun connect() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loginToMicrosoftAccount(authorizationCode: String) {
|
||||||
|
Log.verbose("Logging into microsoft account...")
|
||||||
|
try {
|
||||||
|
val authorizationToken = getAuthorizationToken(authorizationCode)
|
||||||
|
val xboxLiveToken = getXboxLiveToken(authorizationToken)
|
||||||
|
val xstsToken = getXSTSToken(xboxLiveToken.first)
|
||||||
|
|
||||||
|
val microsoftAccount = getMicrosoftAccount(getMinecraftAccessToken(xboxLiveToken.second, xstsToken))
|
||||||
|
Account.addAccount(microsoftAccount)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.warn("Can not login into microsoft account")
|
||||||
|
exception.printStackTrace()
|
||||||
|
|
||||||
|
if (!StaticConfiguration.HEADLESS_MODE) {
|
||||||
|
var message = "Could not login!"
|
||||||
|
var errorMessage = exception.javaClass.canonicalName + ": " + exception.message
|
||||||
|
if (exception is LoginException) {
|
||||||
|
message = "${exception.message} (${exception.errorCode})"
|
||||||
|
errorMessage = exception.errorMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater {
|
||||||
|
val dialog = JFXAlert<Boolean>()
|
||||||
|
GUITools.initializePane(dialog.dialogPane)
|
||||||
|
// Do not translate this, translations might fail to load...
|
||||||
|
dialog.title = "Login error"
|
||||||
|
val layout = JFXDialogLayout()
|
||||||
|
layout.setHeading(Text(message))
|
||||||
|
val text = TextArea(errorMessage)
|
||||||
|
text.isEditable = false
|
||||||
|
text.isWrapText = true
|
||||||
|
layout.setBody(text)
|
||||||
|
dialog.dialogPane.content = layout
|
||||||
|
val stage = dialog.dialogPane.scene.window as Stage
|
||||||
|
stage.toFront()
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAuthorizationToken(authorizationCode: String): String {
|
||||||
|
val data = mapOf(
|
||||||
|
"client_id" to ProtocolDefinition.MICROSOFT_ACCOUNT_APPLICATION_ID,
|
||||||
|
"code" to authorizationCode,
|
||||||
|
"grant_type" to "authorization_code",
|
||||||
|
"scope" to "service::user.auth.xboxlive.com::MBI_SSL",
|
||||||
|
)
|
||||||
|
val response = HTTP.postData(ProtocolDefinition.MICROSOFT_ACCOUNT_AUTH_TOKEN_URL, HashMap(data))
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw LoginException(response.statusCode(), "Could not get authorization token ", response.body())
|
||||||
|
}
|
||||||
|
val body = JsonParser.parseString(response.body()).asJsonObject
|
||||||
|
return body["access_token"]!!.asString
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns A: XBL Token; B: UHS Token
|
||||||
|
*/
|
||||||
|
fun getXboxLiveToken(authorizationToken: String): Pair<String, String> {
|
||||||
|
val payload = mapOf(
|
||||||
|
"Properties" to mapOf(
|
||||||
|
"AuthMethod" to "RPS",
|
||||||
|
"SiteName" to "user.auth.xboxlive.com",
|
||||||
|
"RpsTicket" to authorizationToken
|
||||||
|
),
|
||||||
|
"RelyingParty" to "http://auth.xboxlive.com",
|
||||||
|
"TokenType" to "JWT",
|
||||||
|
)
|
||||||
|
val response = HTTP.postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_XBOX_LIVE_AUTHENTICATE_URL, Util.GSON.toJson(payload))
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw LoginException(response.statusCode(), "Could not get authenticate against xbox live ", response.body())
|
||||||
|
}
|
||||||
|
val body = JsonParser.parseString(response.body()).asJsonObject
|
||||||
|
return Pair(body["Token"]!!.asString, body["DisplayClaims"].asJsonObject["xui"].asJsonArray[0].asJsonObject["uhs"].asString)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getXSTSToken(xBoxLiveToken: String): String {
|
||||||
|
val payload = mapOf(
|
||||||
|
"Properties" to mapOf(
|
||||||
|
"SandboxId" to "RETAIL",
|
||||||
|
"UserTokens" to listOf(xBoxLiveToken)
|
||||||
|
),
|
||||||
|
"RelyingParty" to "rp://api.minecraftservices.com/",
|
||||||
|
"TokenType" to "JWT",
|
||||||
|
)
|
||||||
|
val response = HTTP.postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_XSTS_URL, Util.GSON.toJson(payload))
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
val error = JsonParser.parseString(response.body()).asJsonObject
|
||||||
|
val errorMessage = when (error["XErr"].asLong) {
|
||||||
|
2148916233 -> "You don't have an XBox account!"
|
||||||
|
2148916238 -> "This account is a child account!"
|
||||||
|
else -> error["Message"].asString
|
||||||
|
}
|
||||||
|
throw LoginException(response.statusCode(), "Could not get authenticate against XSTS token ", errorMessage)
|
||||||
|
}
|
||||||
|
val body = JsonParser.parseString(response.body()).asJsonObject
|
||||||
|
return body["Token"].asString!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMinecraftAccessToken(uhs: String, xstsToken: String): String {
|
||||||
|
val payload = mapOf(
|
||||||
|
"identityToken" to "XBL3.0 x=${uhs};${xstsToken}"
|
||||||
|
)
|
||||||
|
val response = HTTP.postJson(ProtocolDefinition.MICROSOFT_ACCOUNT_MINECRAFT_LOGIN_WITH_XBOX_URL, Util.GSON.toJson(payload))
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
val error = JsonParser.parseString(response.body()).asJsonObject
|
||||||
|
throw LoginException(response.statusCode(), "Could not get minecraft access token ", error["errorMessage"].asString)
|
||||||
|
}
|
||||||
|
val body = JsonParser.parseString(response.body()).asJsonObject
|
||||||
|
return body["access_token"].asString!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMicrosoftAccount(bearerToken: String): MicrosoftAccount {
|
||||||
|
val response = HTTP.get(ProtocolDefinition.MICROSOFT_ACCOUNT_GET_MOJANG_PROFILE_URL, HashMap(mapOf(
|
||||||
|
"Authorization" to "Bearer $bearerToken"
|
||||||
|
)))
|
||||||
|
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
val errorMessage = when (response.statusCode()) {
|
||||||
|
404 -> "You don't have a copy of minecraft!"
|
||||||
|
else -> JsonParser.parseString(response.body()).asJsonObject["errorMessage"].asString
|
||||||
|
}
|
||||||
|
throw LoginException(response.statusCode(), "Could not get minecraft profile", errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = JsonParser.parseString(response.body()).asJsonObject
|
||||||
|
return MicrosoftAccount(bearerToken, body["id"].asString!!, Util.getUUIDFromString(body["id"].asString!!), body["name"].asString!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
URL.setURLStreamHandlerFactory {
|
||||||
|
if (it == "ms-xal-" + ProtocolDefinition.MICROSOFT_ACCOUNT_APPLICATION_ID) {
|
||||||
|
return@setURLStreamHandlerFactory object : URLStreamHandler() {
|
||||||
|
override fun openConnection(url: URL): URLConnection {
|
||||||
|
loginToMicrosoftAccount(Util.urlQueryToMap(url.query)["code"]!!)
|
||||||
|
return NULL_URL_CONNECTION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@setURLStreamHandlerFactory null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,7 @@
|
|||||||
"SERVER_ACTION_SESSIONS": "Verbindungen",
|
"SERVER_ACTION_SESSIONS": "Verbindungen",
|
||||||
"SERVER_ACTION_DELETE": "Löschen",
|
"SERVER_ACTION_DELETE": "Löschen",
|
||||||
"SESSIONS_ACTION_DISCONNECT": "Trennen",
|
"SESSIONS_ACTION_DISCONNECT": "Trennen",
|
||||||
|
"ACCOUNT_MODAL_MENU_ADD_MICROSOFT_ACCOUNT": "Anmelden (Microsoft)",
|
||||||
"ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT": "Anmelden (Mojang)",
|
"ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT": "Anmelden (Mojang)",
|
||||||
"ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT": "Hinzufügen (Offline)",
|
"ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT": "Hinzufügen (Offline)",
|
||||||
"MAIN_WINDOW_TITLE": "Minosoft",
|
"MAIN_WINDOW_TITLE": "Minosoft",
|
||||||
@ -63,6 +64,7 @@
|
|||||||
"LOGIN_OFFLINE_USERNAME": "Benutzername",
|
"LOGIN_OFFLINE_USERNAME": "Benutzername",
|
||||||
"LOGIN_OFFLINE_UUID": "(UUID)",
|
"LOGIN_OFFLINE_UUID": "(UUID)",
|
||||||
"LOGIN_OFFLINE_ADD_BUTTON": "Hinzufügen",
|
"LOGIN_OFFLINE_ADD_BUTTON": "Hinzufügen",
|
||||||
|
"LOGIN_MICROSOFT_DIALOG_TITLE": "Anmelden (Microsoft) - Mojang",
|
||||||
"ERROR": "Fehler",
|
"ERROR": "Fehler",
|
||||||
"MINOSOFT_STILL_STARTING_TITLE": "Bitte warten",
|
"MINOSOFT_STILL_STARTING_TITLE": "Bitte warten",
|
||||||
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft muss noch ein paar Dinge erledigen, bevor du es verwenden kannst.\nBitte warte noch ein paar Sekunden...",
|
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft muss noch ein paar Dinge erledigen, bevor du es verwenden kannst.\nBitte warte noch ein paar Sekunden...",
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"SERVER_ACTION_SESSIONS": "Sessions",
|
"SERVER_ACTION_SESSIONS": "Sessions",
|
||||||
"SERVER_ACTION_DELETE": "Delete",
|
"SERVER_ACTION_DELETE": "Delete",
|
||||||
"SESSIONS_ACTION_DISCONNECT": "Disconnect",
|
"SESSIONS_ACTION_DISCONNECT": "Disconnect",
|
||||||
|
"ACCOUNT_MODAL_MENU_ADD_MICROSOFT_ACCOUNT": "Login (Microsoft)",
|
||||||
"ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT": "Login (Mojang)",
|
"ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT": "Login (Mojang)",
|
||||||
"ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT": "Add (Offline)",
|
"ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT": "Add (Offline)",
|
||||||
"MAIN_WINDOW_TITLE": "Minosoft",
|
"MAIN_WINDOW_TITLE": "Minosoft",
|
||||||
@ -64,6 +65,7 @@
|
|||||||
"LOGIN_OFFLINE_USERNAME": "Username",
|
"LOGIN_OFFLINE_USERNAME": "Username",
|
||||||
"LOGIN_OFFLINE_UUID": "(UUID)",
|
"LOGIN_OFFLINE_UUID": "(UUID)",
|
||||||
"LOGIN_OFFLINE_ADD_BUTTON": "Add",
|
"LOGIN_OFFLINE_ADD_BUTTON": "Add",
|
||||||
|
"LOGIN_MICROSOFT_DIALOG_TITLE": "Login (Microsoft) - Minosoft",
|
||||||
"ERROR": "Error",
|
"ERROR": "Error",
|
||||||
"MINOSOFT_STILL_STARTING_TITLE": "Please wait",
|
"MINOSOFT_STILL_STARTING_TITLE": "Please wait",
|
||||||
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...",
|
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...",
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="640.0" fx:controller="de.bixilon.minosoft.gui.main.AccountWindow">
|
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="640.0" fx:controller="de.bixilon.minosoft.gui.main.AccountWindow">
|
||||||
<MenuBar VBox.vgrow="NEVER">
|
<MenuBar VBox.vgrow="NEVER">
|
||||||
<Menu mnemonicParsing="false" text="_Accounts">
|
<Menu mnemonicParsing="false" text="_Accounts">
|
||||||
<MenuItem fx:id="menuAddMojangAccount" mnemonicParsing="false" onAction="#addMojangAccount" text="-Add Mojang account-"/>
|
<MenuItem fx:id="menuAddMicrosoftAccount" mnemonicParsing="false" onAction="#addMicrosoftAccount" text="-Add Microsoft account-"/>
|
||||||
<MenuItem fx:id="menuAddOfflineAccount" mnemonicParsing="false" onAction="#addOfflineAccount" text="-Add Offline account-"/>
|
<MenuItem fx:id="menuAddOfflineAccount" mnemonicParsing="false" onAction="#addOfflineAccount" text="-Add Offline account-"/>
|
||||||
|
<MenuItem fx:id="menuAddMojangAccount" mnemonicParsing="false" onAction="#addMojangAccount" text="-Add Mojang account-"/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</MenuBar>
|
</MenuBar>
|
||||||
<AnchorPane VBox.vgrow="ALWAYS">
|
<AnchorPane VBox.vgrow="ALWAYS">
|
||||||
|
7
src/main/resources/layout/dialogs/login/microsoft.fxml
Normal file
7
src/main/resources/layout/dialogs/login/microsoft.fxml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.web.WebView?>
|
||||||
|
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="hBox" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/15.0.1" fx:controller="de.bixilon.minosoft.gui.main.dialogs.login.MicrosoftLoginController">
|
||||||
|
<WebView fx:id="webView" minHeight="100.0" minWidth="100.0" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="ALWAYS"/>
|
||||||
|
</HBox>
|
@ -7,7 +7,7 @@
|
|||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="hBox" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="de.bixilon.minosoft.gui.main.dialogs.MojangLoginController">
|
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="hBox" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="de.bixilon.minosoft.gui.main.dialogs.login.MojangLoginController">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||||
</padding>
|
</padding>
|
@ -6,7 +6,7 @@
|
|||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="hBox" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="de.bixilon.minosoft.gui.main.dialogs.OfflineLoginController">
|
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="hBox" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="de.bixilon.minosoft.gui.main.dialogs.login.OfflineLoginController">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||||
</padding>
|
</padding>
|
Loading…
x
Reference in New Issue
Block a user