Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持通过 OAuth 登录 Littleskin #3491

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions HMCL/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" els
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: ""
val littleSkinClientId = System.getenv("LITTLT_SKIN_CLIENT_ID") ?: "866" // TODO

version = "$versionRoot.$buildNumber"

Expand Down Expand Up @@ -128,6 +129,7 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
"Microsoft-Auth-Id" to microsoftAuthId,
"Microsoft-Auth-Secret" to microsoftAuthSecret,
"CurseForge-Api-Key" to curseForgeApiKey,
"LittleSkin-Client-Id" to littleSkinClientId,
"Build-Channel" to versionType,
"Class-Path" to "pack200.jar",
"Add-Opens" to listOf(
Expand Down
34 changes: 24 additions & 10 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;

import java.io.IOException;
Expand Down Expand Up @@ -123,28 +122,39 @@ public static class Factory implements OAuth.Callback {
public final EventManager<GrantDeviceCodeEvent> onGrantDeviceCode = new EventManager<>();
public final EventManager<OpenBrowserEvent> onOpenBrowser = new EventManager<>();

private final String clientId;
private final String clientSecret;

public Factory(String clientId, String clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
}

@Override
public OAuth.Session startServer() throws IOException, AuthenticationException {
if (StringUtils.isBlank(getClientId())) {
throw new MicrosoftAuthenticationNotSupportedException();
}

IOException exception = null;
for (int port : new int[]{29111, 29112, 29113, 29114, 29115}) {
for (int port = 29111; port < 29116; port++) {
try {
OAuthServer server = new OAuthServer(port);
server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true);
return server;
} catch (IOException e) {
exception = e;
if (exception == null) {
exception = new IOException();
}
exception.addSuppressed(e);
}
}
throw exception;
}

@Override
public void grantDeviceCode(String userCode, String verificationURI) {
onGrantDeviceCode.fireEvent(new GrantDeviceCodeEvent(this, userCode, verificationURI));
public void grantDeviceCode(String userCode, String verificationURI, String verificationUriComplete) {
onGrantDeviceCode.fireEvent(new GrantDeviceCodeEvent(this, userCode, verificationURI, verificationUriComplete));
}

@Override
Expand All @@ -157,14 +167,12 @@ public void openBrowser(String url) throws IOException {

@Override
public String getClientId() {
return System.getProperty("hmcl.microsoft.auth.id",
JarUtils.getManifestAttribute("Microsoft-Auth-Id", ""));
return clientId;
}

@Override
public String getClientSecret() {
return System.getProperty("hmcl.microsoft.auth.secret",
JarUtils.getManifestAttribute("Microsoft-Auth-Secret", ""));
return clientSecret;
}

@Override
Expand All @@ -176,11 +184,13 @@ public boolean isPublicClient() {
public static class GrantDeviceCodeEvent extends Event {
private final String userCode;
private final String verificationUri;
private final String verificationUriComplete;

public GrantDeviceCodeEvent(Object source, String userCode, String verificationUri) {
public GrantDeviceCodeEvent(Object source, String userCode, String verificationUri, String verificationUriComplete) {
super(source);
this.userCode = userCode;
this.verificationUri = verificationUri;
this.verificationUriComplete = verificationUriComplete;
}

public String getUserCode() {
Expand All @@ -190,6 +200,10 @@ public String getUserCode() {
public String getVerificationUri() {
return verificationUri;
}

public String getVerificationUriComplete() {
return verificationUriComplete;
}
}

public static class OpenBrowserEvent extends Event {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
import org.jackhuang.hmcl.auth.littleskin.LittleSkinAccount;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.Skin;
Expand Down Expand Up @@ -355,7 +356,7 @@ public static void bindAvatar(Canvas canvas, YggdrasilService service, UUID uuid
}

public static void bindAvatar(Canvas canvas, Account account) {
if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount || account instanceof OfflineAccount)
if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount || account instanceof LittleSkinAccount || account instanceof OfflineAccount)
fxAvatarBinding(canvas, skinBinding(account));
else {
unbindAvatar(canvas);
Expand Down
40 changes: 25 additions & 15 deletions HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.authlibinjector.*;
import org.jackhuang.hmcl.auth.littleskin.LittleSkinAccount;
import org.jackhuang.hmcl.auth.littleskin.LittleSkinAccountFactory;
import org.jackhuang.hmcl.auth.littleskin.LittleSkinService;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftService;
Expand All @@ -37,6 +40,7 @@
import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.skin.InvalidSkinException;

import javax.net.ssl.SSLException;
Expand Down Expand Up @@ -80,12 +84,23 @@ private static void triggerAuthlibInjectorUpdateCheck() {
}
}

public static final OAuthServer.Factory OAUTH_CALLBACK = new OAuthServer.Factory();
public static final OAuthServer.Factory MICROSOFT_OAUTH_CALLBACK = new OAuthServer.Factory(
System.getProperty("hmcl.microsoft.auth.id",
JarUtils.getManifestAttribute("Microsoft-Auth-Id", "")),
System.getProperty("hmcl.microsoft.auth.secret",
JarUtils.getManifestAttribute("Microsoft-Auth-Secret", ""))
);

public static final OAuthServer.Factory LITTLE_SKIN_CALLBACK = new OAuthServer.Factory(
JarUtils.getManifestAttribute("LittleSkin-Client-Id", ""),
""
);

public static final OfflineAccountFactory FACTORY_OFFLINE = new OfflineAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER);
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer);
public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK));
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(MICROSOFT_OAUTH_CALLBACK));
public static final LittleSkinAccountFactory FACTORY_LITTLE_SKIN = new LittleSkinAccountFactory(new LittleSkinService(LITTLE_SKIN_CALLBACK));
public static final List<AccountFactory<?>> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_LITTLE_SKIN, FACTORY_AUTHLIB_INJECTOR);

// ==== login type / account factory mapping ====
private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();
Expand All @@ -95,6 +110,7 @@ private static void triggerAuthlibInjectorUpdateCheck() {
type2factory.put("offline", FACTORY_OFFLINE);
type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR);
type2factory.put("microsoft", FACTORY_MICROSOFT);
type2factory.put("littleskin", FACTORY_LITTLE_SKIN);

type2factory.forEach((type, factory) -> factory2type.put(factory, type));
}
Expand Down Expand Up @@ -127,6 +143,8 @@ else if (account instanceof AuthlibInjectorAccount)
return FACTORY_AUTHLIB_INJECTOR;
else if (account instanceof MicrosoftAccount)
return FACTORY_MICROSOFT;
else if (account instanceof LittleSkinAccount)
return FACTORY_LITTLE_SKIN;
else
throw new IllegalArgumentException("Failed to determine account type: " + account);
}
Expand Down Expand Up @@ -221,16 +239,6 @@ static void init() {
if (initialized)
throw new IllegalStateException("Already initialized");

if (!config().isAddedLittleSkin()) {
AuthlibInjectorServer littleSkin = new AuthlibInjectorServer("https://littleskin.cn/api/yggdrasil/");

if (config().getAuthlibInjectorServers().stream().noneMatch(it -> littleSkin.getUrl().equals(it.getUrl()))) {
config().getAuthlibInjectorServers().add(0, littleSkin);
}

config().setAddedLittleSkin(true);
}

loadGlobalAccountStorages();

// load accounts
Expand Down Expand Up @@ -409,7 +417,9 @@ private static void removeDanglingAuthlibInjectorAccounts() {
private static final Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(
pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"),
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"),
pair(Accounts.FACTORY_MICROSOFT, "account.methods.microsoft"));
pair(Accounts.FACTORY_MICROSOFT, "account.methods.microsoft"),
pair(Accounts.FACTORY_LITTLE_SKIN, "account.methods.littleskin")
);

public static String getLocalizedLoginTypeName(AccountFactory<?> factory) {
return i18n(Optional.ofNullable(unlocalizedLoginTypeNames.get(factory))
Expand Down Expand Up @@ -473,7 +483,7 @@ public static String localizeErrorMessage(Exception exception) {
} else if (exception instanceof MicrosoftService.NoXuiException) {
return i18n("account.methods.microsoft.error.add_family_probably");
} else if (exception instanceof OAuthServer.MicrosoftAuthenticationNotSupportedException) {
return i18n("account.methods.microsoft.snapshot");
return i18n("account.methods.snapshot");
} else if (exception instanceof OAuthAccount.WrongAccountException) {
return i18n("account.failed.wrong_account");
} else if (exception.getClass() == AuthenticationException.class) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.littleskin.LittleSkinService;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
Expand Down Expand Up @@ -115,12 +116,23 @@ public AccountListPageSkin(AccountListPage skinnable) {
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
boxMethods.getChildren().add(microsoftItem);

AdvancedListItem littleSkinItem = new AdvancedListItem();
littleSkinItem.getStyleClass().add("navigation-drawer-item");
littleSkinItem.setActionButtonVisible(false);
littleSkinItem.setTitle("LittleSkin");
littleSkinItem.setLeftGraphic(wrap(SVG.SERVER));
littleSkinItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_LITTLE_SKIN)));
boxMethods.getChildren().add(littleSkinItem);

VBox boxAuthServers = new VBox();
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
AdvancedListItem item = new AdvancedListItem();
item.getStyleClass().add("navigation-drawer-item");
item.setLeftGraphic(wrap(SVG.SERVER));
item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server)));
if (LittleSkinService.API_ROOT.equals(server.getUrl())) {
item.setVisible(false);
}

JFXButton btnRemove = new JFXButton();
btnRemove.setOnAction(e -> {
Expand Down
Loading
Loading