diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java index 8e5325b04..2a3217206 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -22,7 +22,7 @@ import org.jackhuang.hmcl.auth.AuthenticationException; import org.jackhuang.hmcl.auth.OAuth; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.event.EventManager; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.theme.Themes; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.JarUtils; @@ -37,8 +37,8 @@ import java.util.concurrent.ExecutionException; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.thread; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class OAuthServer extends NanoHTTPD implements OAuth.Session { private final int port; @@ -104,8 +104,11 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { String html; try { html = IOUtils.readFullyAsString(OAuthServer.class.getResourceAsStream("/assets/microsoft_auth.html")) + .replace("%style%", Themes.getTheme().toColorScheme().toStyleSheet().replace("-monet", "--monet")) .replace("%lang%", Locale.getDefault().toLanguageTag()) - .replace("%close-page%", i18n("account.methods.microsoft.close_page")); + .replace("%success%", i18n("message.success")) + .replace("%ok%", i18n("button.ok")) + .replace("%close_page%", i18n("account.methods.microsoft.close_page")); } catch (IOException e) { LOG.error("Failed to load html", e); return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_HTML, ""); @@ -123,7 +126,8 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { public static class Factory implements OAuth.Callback { public final EventManager onGrantDeviceCode = new EventManager<>(); - public final EventManager onOpenBrowser = new EventManager<>(); + public final EventManager onOpenBrowserAuthorizationCode = new EventManager<>(); + public final EventManager onOpenBrowserDevice = new EventManager<>(); @Override public OAuth.Session startServer() throws IOException, AuthenticationException { @@ -150,17 +154,13 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { } @Override - public void openBrowser(String url) throws IOException { + public void openBrowser(OAuth.GrantFlow grantFlow, String url) throws IOException { lastlyOpenedURL = url; - try { - Thread.sleep(1500); - } catch (InterruptedException ignored) { + switch (grantFlow) { + case AUTHORIZATION_CODE -> onOpenBrowserAuthorizationCode.fireEvent(new OpenBrowserEvent(this, url)); + case DEVICE -> onOpenBrowserDevice.fireEvent(new OpenBrowserEvent(this, url)); } - - FXUtils.openLink(url); - - onOpenBrowser.fireEvent(new OpenBrowserEvent(this, url)); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java index 9fce1daf9..a65b24b66 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.ui; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.ui.account.ClassicAccountLoginDialog; -import org.jackhuang.hmcl.ui.account.OAuthAccountLoginDialog; +import org.jackhuang.hmcl.ui.account.MicrosoftAccountLoginPane; import java.util.Optional; import java.util.concurrent.CancellationException; @@ -49,10 +49,10 @@ public final class DialogController { CountDownLatch latch = new CountDownLatch(1); AtomicReference res = new AtomicReference<>(null); runInFX(() -> { - OAuthAccountLoginDialog pane = new OAuthAccountLoginDialog((OAuthAccount) account, it -> { + MicrosoftAccountLoginPane pane = new MicrosoftAccountLoginPane(account, it -> { res.set(it); latch.countDown(); - }, latch::countDown); + }, latch::countDown, false); Controllers.dialog(pane); }); latch.await(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java index 691ae16d7..bdcaff5f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -130,7 +130,7 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco microsoftItem.getStyleClass().add("navigation-drawer-item"); microsoftItem.setTitle(i18n("account.methods.microsoft")); microsoftItem.setLeftIcon(SVG.MICROSOFT); - microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT))); + microsoftItem.setOnAction(e -> Controllers.dialog(new MicrosoftAccountLoginPane())); AdvancedListItem offlineItem = new AdvancedListItem(); offlineItem.getStyleClass().add("navigation-drawer-item"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index 98ddaf398..be15f7da6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -23,9 +23,7 @@ import javafx.application.Platform; import javafx.beans.NamedArg; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -45,7 +43,6 @@ import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory; import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; -import org.jackhuang.hmcl.game.OAuthServer; import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.task.Schedulers; @@ -54,7 +51,6 @@ import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.upgrade.IntegrityChecker; import org.jackhuang.hmcl.util.StringUtils; @@ -85,13 +81,11 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware { private final JFXButton btnAccept; private final SpinnerPane spinner; private final Node body; - + private final HBox actions; private Node detailsPane; // AccountDetailsInputPane for Offline / Mojang / authlib-injector, Label for Microsoft private final Pane detailsContainer; private final BooleanProperty logging = new SimpleBooleanProperty(); - private final ObjectProperty deviceCode = new SimpleObjectProperty<>(); - private final WeakListenerHolder holder = new WeakListenerHolder(); private TaskExecutor loginTask; @@ -146,10 +140,10 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware { btnCancel.setOnAction(e -> onCancel()); onEscPressed(this, btnCancel::fire); - HBox hbox = new HBox(spinner, btnCancel); - hbox.setAlignment(Pos.CENTER_RIGHT); + actions = new HBox(spinner, btnCancel); + actions.setAlignment(Pos.CENTER_RIGHT); - setActions(lblErrorMessage, hbox); + setActions(lblErrorMessage, actions); } if (showMethodSwitcher) { @@ -226,7 +220,6 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware { Runnable doCreate = () -> { logging.set(true); - deviceCode.set(null); loginTask = Task.supplyAsync(() -> factory.create(new DialogCharacterSelector(), username, password, null, additionalData)) .whenComplete(Schedulers.javafx(), account -> { @@ -281,88 +274,22 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware { detailsContainer.getChildren().remove(detailsPane); lblErrorMessage.setText(""); lblErrorMessage.setVisible(true); + actions.setVisible(true); + actions.setVisible(true); } if (factory == Accounts.FACTORY_MICROSOFT) { VBox vbox = new VBox(8); detailsPane = vbox; - - if (Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) { - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING); - hintPane.setSegment(i18n("account.methods.microsoft.snapshot")); - vbox.getChildren().add(hintPane); - return; - } - - if (!IntegrityChecker.isOfficial()) { - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING); - hintPane.setSegment(i18n("unofficial.hint")); - vbox.getChildren().add(hintPane); - } - - VBox codeBox = new VBox(8); - Label hint = new Label(i18n("account.methods.microsoft.code")); - Label code = new Label(); - code.setMouseTransparent(true); - code.setStyle("-fx-font-size: 24"); - codeBox.getChildren().addAll(hint, code); - codeBox.setAlignment(Pos.CENTER); - vbox.getChildren().add(codeBox); - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); - HintPane errHintPane = new HintPane(MessageDialogPane.MessageType.ERROR); - errHintPane.setVisible(false); - errHintPane.setManaged(false); - - codeBox.setVisible(false); - codeBox.setManaged(false); - - FXUtils.onChangeAndOperate(deviceCode, deviceCode -> { - if (deviceCode != null) { - FXUtils.copyText(deviceCode.getUserCode()); - code.setText(deviceCode.getUserCode()); - hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getVerificationUri())); - codeBox.setVisible(true); - codeBox.setManaged(true); - } else { - hintPane.setSegment(i18n("account.methods.microsoft.hint")); - codeBox.setVisible(false); - codeBox.setManaged(false); - } + hintPane.setText(i18n("account.methods.microsoft.hint")); + vbox.getChildren().addAll(new MicrosoftAccountLoginPane(true)); + btnAccept.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + Controllers.dialog(new MicrosoftAccountLoginPane()); }); - - lblErrorMessage.setVisible(false); - lblErrorMessage.textProperty().addListener((obs, oldVal, newVal) -> { - boolean hasError = !newVal.isEmpty(); - errHintPane.setSegment(newVal); - errHintPane.setVisible(hasError); - errHintPane.setManaged(hasError); - hintPane.setVisible(!hasError); - hintPane.setManaged(!hasError); - codeBox.setVisible(!hasError && deviceCode.get() != null); - codeBox.setManaged(!hasError && deviceCode.get() != null); - }); - - FXUtils.onClicked(codeBox, () -> { - if (deviceCode.get() != null) FXUtils.copyText(deviceCode.get().getUserCode()); - }); - - holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value -> - runInFX(() -> deviceCode.set(value)) - )); - - HBox linkBox = new HBox(); - JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile")); - profileLink.setExternalLink("https://account.live.com/editprof.aspx"); - JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase")); - purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL); - JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize")); - deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A"); - JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password")); - forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx"); - linkBox.getChildren().setAll(profileLink, purchaseLink, deauthorizeLink, forgotpasswordLink); - - vbox.getChildren().addAll(hintPane, errHintPane, linkBox); + actions.setManaged(false); + actions.setVisible(false); btnAccept.setDisable(false); } else { detailsPane = new AccountDetailsInputPane(factory, btnAccept::fire); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java new file mode 100644 index 000000000..9273d5a62 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginPane.java @@ -0,0 +1,315 @@ +package org.jackhuang.hmcl.ui.account; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialogLayout; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.control.Separator; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.scene.shape.FillRule; +import javafx.scene.shape.SVGPath; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.OAuth; +import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; +import org.jackhuang.hmcl.game.OAuthServer; +import org.jackhuang.hmcl.setting.Accounts; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.theme.Themes; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WeakListenerHolder; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.upgrade.IntegrityChecker; +import org.jackhuang.hmcl.util.Lang; + +import java.util.concurrent.CancellationException; +import java.util.function.Consumer; + +import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.ui.FXUtils.runInFX; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class MicrosoftAccountLoginPane extends JFXDialogLayout implements DialogAware { + private final Account accountToRelogin; + private final Consumer loginCallback; + private final Runnable cancelCallback; + + @SuppressWarnings("FieldCanBeLocal") + private final WeakListenerHolder holder = new WeakListenerHolder(); + private final ObjectProperty deviceCode = new SimpleObjectProperty<>(); + private final ObjectProperty browserUrl = new SimpleObjectProperty<>(); + private final Label lblCode; + + private TaskExecutor browserTaskExecutor; + private TaskExecutor deviceTaskExecutor; + + private final SpinnerPane loginButtonSpinner; + private final HBox authMethodsContentBox = new HBox(0); + private final HintPane errHintPane = new HintPane(MessageDialogPane.MessageType.ERROR); + private HintPane unofficialHintPane; + + public MicrosoftAccountLoginPane() { + this(false); + } + + public MicrosoftAccountLoginPane(boolean bodyonly) { + this(null, null, null, bodyonly); + } + + public MicrosoftAccountLoginPane(Account account, Consumer callback, Runnable onCancel, boolean bodyonly) { + this.accountToRelogin = account; + this.loginCallback = callback; + this.cancelCallback = onCancel; + + getStyleClass().add("microsoft-login-dialog"); + if (!bodyonly) { + Label heading = new Label(accountToRelogin != null ? i18n("account.login.refresh") : i18n("account.create.microsoft")); + heading.getStyleClass().add("header-label"); + setHeading(heading); + } else { + setStyle("-fx-padding: 0px 0px 0px 0px;"); + } + + onEscPressed(this, this::onCancel); + + JFXButton btnLogin = new JFXButton(i18n("account.login")); + btnLogin.getStyleClass().add("dialog-accept"); + btnLogin.setOnAction(e -> startLoginTasks()); + + loginButtonSpinner = new SpinnerPane(); + loginButtonSpinner.getStyleClass().add("small-spinner-pane"); + loginButtonSpinner.setContent(btnLogin); + + lblCode = new Label(); + lblCode.getStyleClass().add("code-label"); + lblCode.setStyle("-fx-font-family: \"" + Lang.requireNonNullElse(config().getFontFamily(), FXUtils.DEFAULT_MONOSPACE_FONT) + "\""); + + JFXButton btnCancel = new JFXButton(i18n("button.cancel")); + btnCancel.getStyleClass().add("dialog-cancel"); + btnCancel.setOnAction(e -> onCancel()); + + HBox actions = new HBox(10, loginButtonSpinner, btnCancel); + actions.setAlignment(Pos.CENTER_RIGHT); + setActions(actions); + + VBox rootContainer = new VBox(10); + setBody(rootContainer); + rootContainer.setPadding(new Insets(5, 0, 0, 0)); + rootContainer.setAlignment(Pos.TOP_CENTER); + + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); + hintPane.setText(i18n("account.methods.microsoft.hint")); + FXUtils.onChangeAndOperate(deviceCode, event -> { + if (event != null) + hintPane.setSegment(i18n("account.methods.microsoft.manual", event.getVerificationUri())); + }); + + errHintPane.managedProperty().bind(errHintPane.visibleProperty()); + errHintPane.setVisible(false); + rootContainer.getChildren().add(errHintPane); + + if (Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) { + HintPane snapshotHint = new HintPane(MessageDialogPane.MessageType.WARNING); + snapshotHint.setSegment(i18n("account.methods.microsoft.snapshot")); + rootContainer.getChildren().add(snapshotHint); + btnLogin.setDisable(true); + return; + } + + rootContainer.getChildren().add(hintPane); + + if (!IntegrityChecker.isOfficial()) { + unofficialHintPane = new HintPane(MessageDialogPane.MessageType.WARNING); + unofficialHintPane.managedProperty().bind(unofficialHintPane.visibleProperty()); + unofficialHintPane.setSegment(i18n("unofficial.hint")); + rootContainer.getChildren().add(unofficialHintPane); + } + + initAuthMethodsBox(); + rootContainer.getChildren().add(authMethodsContentBox); + + HBox linkBox = new HBox(15); + linkBox.setAlignment(Pos.CENTER); + linkBox.setPadding(new Insets(5, 0, 0, 0)); + + JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile")); + profileLink.setExternalLink("https://account.live.com/editprof.aspx"); + JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase")); + purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL); + JFXHyperlink forgotLink = new JFXHyperlink(i18n("account.methods.forgot_password")); + forgotLink.setExternalLink("https://account.live.com/ResetPassword.aspx"); + + linkBox.getChildren().addAll(profileLink, purchaseLink, forgotLink); + rootContainer.getChildren().add(linkBox); + + FXUtils.onChangeAndOperate(deviceCode, event -> { + if (event != null) { + authMethodsContentBox.setVisible(true); + lblCode.setText(event.getUserCode()); + } + }); + + holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value -> runInFX(() -> deviceCode.set(value)))); + holder.add(Accounts.OAUTH_CALLBACK.onOpenBrowserAuthorizationCode.registerWeak(event -> runInFX(() -> browserUrl.set(event.getUrl())))); + } + + private void initAuthMethodsBox() { + authMethodsContentBox.managedProperty().bind(authMethodsContentBox.visibleProperty()); + authMethodsContentBox.setAlignment(Pos.CENTER); + authMethodsContentBox.setVisible(false); + authMethodsContentBox.setPrefWidth(600); + + VBox browserPanel = new VBox(10); + browserPanel.setAlignment(Pos.CENTER); + browserPanel.setPadding(new Insets(10)); + browserPanel.setPrefWidth(280); + HBox.setHgrow(browserPanel, Priority.ALWAYS); + + Label browserTitle = new Label(i18n("account.methods.microsoft.methods.browser")); + browserTitle.getStyleClass().add("method-title"); + + Label browserDesc = new Label(i18n("account.methods.microsoft.methods.browser.hint")); + browserDesc.getStyleClass().add("method-desc"); + + JFXButton btnOpenBrowser = FXUtils.newBorderButton(i18n("account.methods.microsoft.methods.browser.copy_open")); + btnOpenBrowser.setOnAction(e -> { + FXUtils.copyText(browserUrl.get()); + FXUtils.openLink(browserUrl.get()); + }); + btnOpenBrowser.disableProperty().bind(browserUrl.isNull()); + + browserPanel.getChildren().addAll(browserTitle, browserDesc, btnOpenBrowser); + + VBox separatorBox = new VBox(); + separatorBox.setAlignment(Pos.CENTER); + separatorBox.setMinWidth(30); + HBox.setHgrow(separatorBox, Priority.NEVER); + + Separator sepTop = new Separator(Orientation.VERTICAL); + VBox.setVgrow(sepTop, Priority.ALWAYS); + + Label orLabel = new Label(i18n("account.methods.microsoft.methods.or")); + orLabel.setPadding(new Insets(5, 0, 5, 0)); + orLabel.setStyle("-fx-text-fill: -monet-outline; -fx-font-size: 11px; -fx-font-weight: bold;"); + + Separator sepBottom = new Separator(Orientation.VERTICAL); + VBox.setVgrow(sepBottom, Priority.ALWAYS); + + separatorBox.getChildren().addAll(sepTop, orLabel, sepBottom); + + VBox devicePanel = new VBox(10); + devicePanel.setAlignment(Pos.CENTER); + devicePanel.setPadding(new Insets(10)); + devicePanel.setPrefWidth(280); + HBox.setHgrow(devicePanel, Priority.ALWAYS); + + Label deviceTitle = new Label(i18n("account.methods.microsoft.methods.device")); + deviceTitle.getStyleClass().add("method-title"); + + Label deviceDesc = new Label(); + deviceDesc.getStyleClass().add("method-desc"); + deviceDesc.textProperty().bind(Bindings.createStringBinding( + () -> i18n("account.methods.microsoft.methods.device.hint", deviceCode.get() == null ? "..." : deviceCode.get().getVerificationUri()), + deviceCode)); + + var qrCode = new SVGPath(); + qrCode.fillProperty().bind(Themes.colorSchemeProperty().getPrimary()); + qrCode.setFillRule(FillRule.EVEN_ODD); + qrCode.setContent("M740 740ZM0 0ZM240 80h20v20h-20zm80 0h20v20h-20zm120 0h20v20h-20zm20 0h20v20h-20zm-180 20h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm-200 20h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm-240 20h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm-200 20h20v20h-20zm100 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm-200 20h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm-240 20h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm-240 20h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm60 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zM80 240h20v20H80zm80 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm-540 20h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm80 0h20v20h-20zm200 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm-480 20h20v20h-20zm80 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zM80 300h20v20H80zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zM80 320h20v20H80zm100 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zM80 340h20v20H80zm60 0h20v20h-20zm80 0h20v20h-20zm80 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm80 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zM80 360h20v20H80zm20 0h20v20h-20zm60 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm140 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zM80 380h20v20H80zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm100 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm120 0h20v20h-20zM80 400h20v20H80zm60 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm120 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zM80 420h20v20H80zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm240 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm-440 20h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm60 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm-460 20h20v20h-20zm80 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm140 0h20v20h-20zM80 480h20v20H80zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm-320 20h20v20h-20zm40 0h20v20h-20zm60 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm20 0h20v20h-20zm80 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm-360 20h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm100 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm-380 20h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm120 0h20v20h-20zm80 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm-380 20h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm100 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm-360 20h20v20h-20zm60 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm80 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm-360 20h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm60 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm-380 20h20v20h-20zm60 0h20v20h-20zm140 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm-360 20h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm20 0h20v20h-20zm20 0h20v20h-20zm40 0h20v20h-20zm40 0h20v20h-20zm60 0h20v20h-20z M80 80h140v140H80Zm20 20h100v100H100Zm420-20h140v140H520Zm20 20h100v100H540Z M120 120h60v60h-60zm440 0h60v60h-60z M80 520h140v140H80Zm20 20h100v100H100Z M120 560h60v60h-60z"); + qrCode.setScaleX(80.0 / 740.0); + qrCode.setScaleY(80.0 / 740.0); + + HBox codeBox = new HBox(10); + codeBox.getStyleClass().add("code-box"); + + FXUtils.setLimitWidth(codeBox, 170); + FXUtils.setLimitHeight(codeBox, 40); + + codeBox.getChildren().add(lblCode); + devicePanel.getChildren().addAll(deviceTitle, deviceDesc, new Group(qrCode), codeBox); + + authMethodsContentBox.getChildren().addAll(browserPanel, separatorBox, devicePanel); + } + + private void startLoginTasks() { + deviceCode.set(null); + browserUrl.set(null); + errHintPane.setVisible(false); + + if (unofficialHintPane != null) { + unofficialHintPane.setVisible(false); + } + + loginButtonSpinner.showSpinner(); + + Task.FinalizedCallbackWithResult onComplete = (account, exception) -> { + if (exception == null) { + cancelAllTasks(); + runInFX(() -> onLoginCompleted(account)); + } else if (!(exception instanceof CancellationException)) { + errHintPane.setText(Accounts.localizeErrorMessage(exception)); + errHintPane.setVisible(true); + authMethodsContentBox.setVisible(false); + } + }; + + browserTaskExecutor = Task.supplyAsync(() -> Accounts.FACTORY_MICROSOFT.create(null, null, null, null, OAuth.GrantFlow.AUTHORIZATION_CODE)) + .whenComplete(Schedulers.javafx(), onComplete) + .executor(true); + + deviceTaskExecutor = Task.supplyAsync(() -> Accounts.FACTORY_MICROSOFT.create(null, null, null, null, OAuth.GrantFlow.DEVICE)) + .whenComplete(Schedulers.javafx(), onComplete) + .executor(true); + } + + private void onLoginCompleted(MicrosoftAccount account) { + if (accountToRelogin != null) Accounts.getAccounts().remove(accountToRelogin); + + int oldIndex = Accounts.getAccounts().indexOf(account); + if (oldIndex == -1) { + Accounts.getAccounts().add(account); + } else { + Accounts.getAccounts().remove(oldIndex); + Accounts.getAccounts().add(oldIndex, account); + } + + Accounts.setSelectedAccount(account); + + if (loginCallback != null) { + try { + loginCallback.accept(account.logIn()); + } catch (AuthenticationException e) { + errHintPane.setText(Accounts.localizeErrorMessage(e)); + errHintPane.setVisible(true); + loginButtonSpinner.showSpinner(); + return; + } + } + fireEvent(new DialogCloseEvent()); + } + + private void cancelAllTasks() { + if (browserTaskExecutor != null) browserTaskExecutor.cancel(); + if (deviceTaskExecutor != null) deviceTaskExecutor.cancel(); + } + + private void onCancel() { + cancelAllTasks(); + if (cancelCallback != null) cancelCallback.run(); + fireEvent(new DialogCloseEvent()); + } +} + diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java deleted file mode 100644 index 667d06385..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/OAuthAccountLoginDialog.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.jackhuang.hmcl.ui.account; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.scene.control.Label; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.auth.AuthInfo; -import org.jackhuang.hmcl.auth.OAuthAccount; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; -import org.jackhuang.hmcl.game.OAuthServer; -import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.WeakListenerHolder; -import org.jackhuang.hmcl.ui.construct.DialogPane; -import org.jackhuang.hmcl.ui.construct.HintPane; -import org.jackhuang.hmcl.ui.construct.JFXHyperlink; -import org.jackhuang.hmcl.ui.construct.MessageDialogPane; - -import java.util.function.Consumer; - -import static org.jackhuang.hmcl.util.logging.Logger.LOG; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class OAuthAccountLoginDialog extends DialogPane { - private final OAuthAccount account; - private final Consumer success; - private final Runnable failed; - private final ObjectProperty deviceCode = new SimpleObjectProperty<>(); - - private final WeakListenerHolder holder = new WeakListenerHolder(); - - public OAuthAccountLoginDialog(OAuthAccount account, Consumer success, Runnable failed) { - this.account = account; - this.success = success; - this.failed = failed; - - setTitle(i18n("account.login.refresh")); - - VBox vbox = new VBox(8); - Label usernameLabel = new Label(account.getUsername()); - - HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO); - FXUtils.onChangeAndOperate(deviceCode, deviceCode -> { - if (deviceCode != null) { - FXUtils.copyText(deviceCode.getUserCode()); - hintPane.setSegment( - "" + i18n("account.login.refresh.microsoft.hint") + "\n" - + i18n("account.methods.microsoft.manual", deviceCode.getUserCode(), deviceCode.getVerificationUri()) - ); - } else { - hintPane.setSegment( - "" + i18n("account.login.refresh.microsoft.hint") + "\n" - + i18n("account.methods.microsoft.hint") - ); - } - }); - FXUtils.onClicked(hintPane, () -> { - if (deviceCode.get() != null) { - FXUtils.copyText(deviceCode.get().getUserCode()); - } - }); - - HBox box = new HBox(8); - JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth")); - birthLink.setExternalLink("https://support.microsoft.com/account-billing/how-to-change-a-birth-date-on-a-microsoft-account-837badbc-999e-54d2-2617-d19206b9540a"); - JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile")); - profileLink.setExternalLink("https://account.live.com/editprof.aspx"); - JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase")); - purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL); - box.getChildren().setAll(profileLink, birthLink, purchaseLink); - GridPane.setColumnSpan(box, 2); - - vbox.getChildren().setAll(usernameLabel, hintPane, box); - setBody(vbox); - - holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(this::onGrantDeviceCode)); - } - - private void onGrantDeviceCode(OAuthServer.GrantDeviceCodeEvent event) { - FXUtils.runInFX(() -> { - deviceCode.set(event); - }); - } - - @Override - protected void onAccept() { - setLoading(); - Task.supplyAsync(account::logInWhenCredentialsExpired) - .whenComplete(Schedulers.javafx(), (authInfo, exception) -> { - if (exception == null) { - success.accept(authInfo); - onSuccess(); - } else { - LOG.info("Failed to login when credentials expired: " + account, exception); - onFailure(Accounts.localizeErrorMessage(exception)); - } - }).start(); - } - - @Override - protected void onCancel() { - failed.run(); - super.onCancel(); - } -} diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 6668a279d..31ae771a9 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -572,6 +572,36 @@ -fx-text-fill: -monet-on-surface; } +/******************************************************************************* + * * + * Microsoft Login Dialog * + * * + ******************************************************************************/ + +.method-title { + -fx-text-fill: -monet-on-surface; + -fx-font-weight: bold; +} + +.method-desc { + -fx-text-fill: -monet-outline; + -fx-font-size: 0.9em; + -fx-line-spacing: 2; + -fx-wrap-text: true; + -fx-text-alignment: center; +} + +.code-box { + -fx-background-color: -monet-surface-variant; + -fx-background-radius: 6; + -fx-alignment: center; +} + +.code-box .code-label { + -fx-font-size: 22px; + -fx-font-weight: bold; + -fx-text-fill: -monet-primary; +} /******************************************************************************* * * diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ca0f544e0..2f682895b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -118,11 +118,14 @@ account.methods.microsoft.error.wrong_verify_method=Failed to log in. Please try account.methods.microsoft.logging_in=Logging in... account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name account.methods.microsoft.hint=Click the "Log in" button to start adding your Microsoft account. -account.methods.microsoft.manual=Please enter the code shown above on the pop-up webpage to complete the login.\n\ - \n\ - If the website fails to load, please open %s manually in your browser.\n\ - \n\ - If your internet connection is bad, it may cause web pages to load slowly or fail to load altogether. You may try again later or switch to a different internet connection. +account.methods.microsoft.methods.or=Or +account.methods.microsoft.methods.device=Log in on another device +account.methods.microsoft.methods.device.hint=Scan the QR code on another device, or visit \n %s \n to complete log in +account.methods.microsoft.methods.device.copy=Copy code +account.methods.microsoft.methods.browser=Log in via browser (Recommended) +account.methods.microsoft.methods.browser.copy_open=Copy link and visit in browser +account.methods.microsoft.methods.browser.hint=Click the following button to copy and open the browser to log in +account.methods.microsoft.manual=If your internet connection is bad, it may cause web pages to load slowly or fail to load altogether.\nYou may try again later or switch to a different internet connection. account.methods.microsoft.profile=Account Profile account.methods.microsoft.purchase=Buy Minecraft account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index e1766747e..94b50e030 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -117,9 +117,14 @@ account.methods.microsoft.error.wrong_verify_method=登入失敗。請在 Micros account.methods.microsoft.logging_in=登入中…… account.methods.microsoft.makegameidsettings=建立檔案 / 編輯檔案名稱 account.methods.microsoft.hint=點擊「登入」按鈕開始新增 Microsoft 帳戶。 -account.methods.microsoft.manual=請在彈出的網頁中輸入上方顯示的代碼以完成登入。\n\ - 若網站未能顯示,請手動在瀏覽器中開啟:%s\n\ - 若網路環境不佳,可能會導致網頁載入緩慢甚至無法載入,請稍後再試或更換網路環境後再試。\n +account.methods.microsoft.methods.or=或 +account.methods.microsoft.methods.device=在其他裝置登入 +account.methods.microsoft.methods.device.hint=在其他裝置掃描 QR Code,或開啟 \n %s \n 完成登入 +account.methods.microsoft.methods.device.copy=複製代碼 +account.methods.microsoft.methods.browser=在瀏覽器登入 (推薦) +account.methods.microsoft.methods.browser.copy_open=複製連結並在瀏覽器中開啟 +account.methods.microsoft.methods.browser.hint=點擊下方按鈕複製連結並開啟瀏覽器以登入 +account.methods.microsoft.manual=若網路環境不佳,可能會導致網頁載入緩慢甚至無法載入,請稍後再試或更換網路環境後再試。 account.methods.microsoft.profile=編輯帳戶個人資訊 account.methods.microsoft.purchase=購買 Minecraft account.methods.forgot_password=忘記密碼 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index e1506c058..c85d17404 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -118,9 +118,14 @@ account.methods.microsoft.error.wrong_verify_method=登录失败。请在微软 account.methods.microsoft.logging_in=登录中…… account.methods.microsoft.makegameidsettings=创建档案 / 编辑档案名称 account.methods.microsoft.hint=点击“登录”按钮开始添加微软账户。 -account.methods.microsoft.manual=请在弹出的网页中输入上方显示的代码以完成登录。\n\ - 若网站未能显示,请手动在浏览器中打开:%s\n\ - 若网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。\n\ +account.methods.microsoft.methods.or=或 +account.methods.microsoft.methods.device=在其他设备登录 +account.methods.microsoft.methods.device.hint=在其他设备扫描二维码,或打开 \n %s \n 完成登录 +account.methods.microsoft.methods.device.copy=复制代码 +account.methods.microsoft.methods.browser=在浏览器登录 (推荐) +account.methods.microsoft.methods.browser.copy_open=复制链接并打开浏览器 +account.methods.microsoft.methods.browser.hint=点击下方按钮复制链接并打开浏览器登录 +account.methods.microsoft.manual=若网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。\n\ 如遇到问题,你可以点击右上角帮助按钮进行求助。 account.methods.microsoft.profile=编辑账户个人信息 account.methods.microsoft.purchase=购买 Minecraft diff --git a/HMCL/src/main/resources/assets/microsoft_auth.html b/HMCL/src/main/resources/assets/microsoft_auth.html index 0cda2b351..1d8a432f5 100644 --- a/HMCL/src/main/resources/assets/microsoft_auth.html +++ b/HMCL/src/main/resources/assets/microsoft_auth.html @@ -19,20 +19,134 @@ along with this program. If not, see . - + + Hello Minecraft! Launcher + -
- %close-page% +
+
+ + +
- - +
+
%success%
+
%close_page%
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java index 9614de6f5..f5d822203 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -77,7 +77,7 @@ public class OAuth { private Result authenticateAuthorizationCode(Options options) throws IOException, InterruptedException, JsonParseException, ExecutionException, AuthenticationException { Session session = options.callback.startServer(); - options.callback.openBrowser(NetworkUtils.withQuery(authorizationURL, + options.callback.openBrowser(GrantFlow.AUTHORIZATION_CODE, NetworkUtils.withQuery(authorizationURL, mapOf(pair("client_id", options.callback.getClientId()), pair("response_type", "code"), pair("redirect_uri", session.getRedirectURI()), pair("scope", options.scope), pair("prompt", "select_account")))); @@ -106,7 +106,7 @@ public class OAuth { options.callback.grantDeviceCode(deviceTokenResponse.userCode, deviceTokenResponse.verificationURI); // Microsoft OAuth Flow - options.callback.openBrowser(deviceTokenResponse.verificationURI); + options.callback.openBrowser(GrantFlow.DEVICE, deviceTokenResponse.verificationURI); long startTime = System.nanoTime(); long interval = TimeUnit.MILLISECONDS.convert(deviceTokenResponse.interval, TimeUnit.SECONDS); @@ -237,9 +237,10 @@ public class OAuth { /** * Open browser * - * @param url OAuth url. + * @param grantFlow the grant flow. + * @param url OAuth url. */ - void openBrowser(String url) throws IOException; + void openBrowser(GrantFlow grantFlow, String url) throws IOException; String getClientId(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java index 5dbb5b751..aa0613805 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java @@ -47,10 +47,10 @@ public final class MicrosoftAccount extends OAuthAccount { this.characterUUID = requireNonNull(session.getProfile().getId()); } - protected MicrosoftAccount(MicrosoftService service, CharacterSelector characterSelector) throws AuthenticationException { + protected MicrosoftAccount(MicrosoftService service, OAuth.GrantFlow flow) throws AuthenticationException { this.service = requireNonNull(service); - MicrosoftSession acquiredSession = service.authenticate(); + MicrosoftSession acquiredSession = service.authenticate(flow); if (acquiredSession.getProfile() == null) { session = service.refresh(acquiredSession); } else { @@ -105,7 +105,7 @@ public final class MicrosoftAccount extends OAuthAccount { @Override public AuthInfo logInWhenCredentialsExpired() throws AuthenticationException { - MicrosoftSession acquiredSession = service.authenticate(); + MicrosoftSession acquiredSession = service.authenticate(OAuth.GrantFlow.DEVICE); if (!Objects.equals(characterUUID, acquiredSession.getProfile().getId())) { throw new WrongAccountException(characterUUID, acquiredSession.getProfile().getId()); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java index e795ec236..f7de93e29 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.auth.microsoft; import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.AuthenticationException; import org.jackhuang.hmcl.auth.CharacterSelector; +import org.jackhuang.hmcl.auth.OAuth; import java.util.Map; import java.util.Objects; @@ -39,9 +40,7 @@ public class MicrosoftAccountFactory extends AccountFactory { @Override public MicrosoftAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException { - Objects.requireNonNull(selector); - - return new MicrosoftAccount(service, selector); + return new MicrosoftAccount(service, (OAuth.GrantFlow) additionalData); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java index 54b5fe765..2174d1087 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java @@ -69,9 +69,9 @@ public class MicrosoftService { return profileRepository; } - public MicrosoftSession authenticate() throws AuthenticationException { + public MicrosoftSession authenticate(OAuth.GrantFlow flow) throws AuthenticationException { try { - OAuth.Result result = OAuth.MICROSOFT.authenticate(OAuth.GrantFlow.DEVICE, new OAuth.Options(SCOPE, callback)); + OAuth.Result result = OAuth.MICROSOFT.authenticate(flow, new OAuth.Options(SCOPE, callback)); return authenticateViaLiveAccessToken(result.getAccessToken(), result.getRefreshToken()); } catch (IOException e) { throw new ServerDisconnectException(e);