From 1dfbb118c508cab19f640124020a00da8e5ec6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:21:01 +0800 Subject: [PATCH 01/17] update --- .../org/jackhuang/hmcl/game/OAuthServer.java | 27 ++++++---- .../java/org/jackhuang/hmcl/auth/OAuth.java | 53 +++++++++++++------ 2 files changed, 54 insertions(+), 26 deletions(-) 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 2a32172069..f162d9b8a0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -29,6 +29,8 @@ import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.IOException; +import java.security.SecureRandom; +import java.util.Base64; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -43,6 +45,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { private final int port; private final CompletableFuture future = new CompletableFuture<>(); + private final String codeVerifier; public static String lastlyOpenedURL; @@ -52,6 +55,19 @@ private OAuthServer(int port) { super(port); this.port = port; + this.codeVerifier = generateCodeVerifier(); + } + + private String generateCodeVerifier() { + SecureRandom sr = new SecureRandom(); + byte[] code = new byte[64]; + sr.nextBytes(code); + return Base64.getUrlEncoder().withoutPadding().encodeToString(code); + } + + @Override + public String getCodeVerifier() { + return codeVerifier; } @Override @@ -168,17 +184,6 @@ public String getClientId() { return System.getProperty("hmcl.microsoft.auth.id", JarUtils.getAttribute("hmcl.microsoft.auth.id", "")); } - - @Override - public String getClientSecret() { - return System.getProperty("hmcl.microsoft.auth.secret", - JarUtils.getAttribute("hmcl.microsoft.auth.secret", "")); - } - - @Override - public boolean isPublicClient() { - return true; // We have turned on the device auth flow. - } } public static class GrantDeviceCodeEvent extends Event { 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 f5d8222031..09223f12fd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -24,6 +24,10 @@ import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -77,17 +81,30 @@ public Result authenticate(GrantFlow grantFlow, Options options) throws Authenti private Result authenticateAuthorizationCode(Options options) throws IOException, InterruptedException, JsonParseException, ExecutionException, AuthenticationException { Session session = options.callback.startServer(); + + String codeVerifier = session.getCodeVerifier(); + String codeChallenge = generateCodeChallenge(codeVerifier); + 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")))); + mapOf(pair("client_id", options.callback.getClientId()), + pair("response_type", "code"), + pair("redirect_uri", session.getRedirectURI()), + pair("scope", options.scope), + pair("prompt", "select_account"), + pair("code_challenge", codeChallenge), + pair("code_challenge_method", "S256") + ))); String code = session.waitFor(); // Authorization Code -> Token AuthorizationResponse response = HttpRequest.POST(accessTokenURL) - .form(pair("client_id", options.callback.getClientId()), pair("code", code), - pair("grant_type", "authorization_code"), pair("client_secret", options.callback.getClientSecret()), - pair("redirect_uri", session.getRedirectURI()), pair("scope", options.scope)) + .form(pair("client_id", options.callback.getClientId()), + pair("code", code), + pair("grant_type", "authorization_code"), + // Replace client_secret with code_verifier + pair("code_verifier", codeVerifier), + pair("redirect_uri", session.getRedirectURI()), + pair("scope", options.scope)) .ignoreHttpCode() .retry(5) .getJson(AuthorizationResponse.class); @@ -153,10 +170,6 @@ public Result refresh(String refreshToken, Options options) throws Authenticatio pair("grant_type", "refresh_token") ); - if (!options.callback.isPublicClient()) { - query.put("client_secret", options.callback.getClientSecret()); - } - RefreshResponse response = HttpRequest.POST(tokenURL) .form(query) .accept("application/json") @@ -174,6 +187,18 @@ public Result refresh(String refreshToken, Options options) throws Authenticatio } } + private static String generateCodeChallenge(String codeVerifier) { + try { + byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII); + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(bytes, 0, bytes.length); + byte[] digest = messageDigest.digest(); + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not available", e); + } + } + private static void handleErrorResponse(ErrorResponse response) throws AuthenticationException { if (response.error == null || response.errorDescription == null) { return; @@ -207,7 +232,7 @@ public Options setUserAgent(String userAgent) { } public interface Session { - + String getCodeVerifier(); String getRedirectURI(); /** @@ -243,12 +268,10 @@ public interface Callback { void openBrowser(GrantFlow grantFlow, String url) throws IOException; String getClientId(); - - String getClientSecret(); - - boolean isPublicClient(); } + // ... Enums and Response classes remain unchanged ... + public enum GrantFlow { AUTHORIZATION_CODE, DEVICE, From c0da4d30662abbce3e02d0c444a535847ea524bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:22:29 +0800 Subject: [PATCH 02/17] update --- .../src/main/java/org/jackhuang/hmcl/auth/OAuth.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 09223f12fd..088c81b460 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -26,7 +26,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -34,6 +33,7 @@ import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class OAuth { public static final OAuth MICROSOFT = new OAuth( @@ -101,7 +101,6 @@ private Result authenticateAuthorizationCode(Options options) throws IOException .form(pair("client_id", options.callback.getClientId()), pair("code", code), pair("grant_type", "authorization_code"), - // Replace client_secret with code_verifier pair("code_verifier", codeVerifier), pair("redirect_uri", session.getRedirectURI()), pair("scope", options.scope)) @@ -194,8 +193,9 @@ private static String generateCodeChallenge(String codeVerifier) { messageDigest.update(bytes, 0, bytes.length); byte[] digest = messageDigest.digest(); return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA-256 algorithm not available", e); + } catch (Exception e) { + LOG.warning("Failed to generate code challenge", e); + throw new RuntimeException(e); } } @@ -270,8 +270,6 @@ public interface Callback { String getClientId(); } - // ... Enums and Response classes remain unchanged ... - public enum GrantFlow { AUTHORIZATION_CODE, DEVICE, From 20a75801d3db56e9c644efe39676d19c4445b286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:28:21 +0800 Subject: [PATCH 03/17] fix: Checkstyle --- HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java | 1 + 1 file changed, 1 insertion(+) 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 088c81b460..907c2b1ccb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -233,6 +233,7 @@ public Options setUserAgent(String userAgent) { public interface Session { String getCodeVerifier(); + String getRedirectURI(); /** From 3dac94a59a133ead890c03e3ae8f7d8f82d19596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:33:10 +0800 Subject: [PATCH 04/17] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 1 + HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java | 1 + 2 files changed, 2 insertions(+) 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 f162d9b8a0..9b911e72b1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -58,6 +58,7 @@ private OAuthServer(int port) { this.codeVerifier = generateCodeVerifier(); } + // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 private String generateCodeVerifier() { SecureRandom sr = new SecureRandom(); byte[] code = new byte[64]; 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 907c2b1ccb..0daf443503 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -187,6 +187,7 @@ public Result refresh(String refreshToken, Options options) throws Authenticatio } private static String generateCodeChallenge(String codeVerifier) { + // https://datatracker.ietf.org/doc/html/rfc7636#section-4.2 try { byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII); MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); From 8fd626cab76f59e3d16ba0b56bc02da96a5222cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:26:00 +0800 Subject: [PATCH 05/17] update --- HMCL/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 96275f51bc..4916ec9e8e 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -28,7 +28,6 @@ val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" els val versionRoot = System.getenv("VERSION_ROOT") ?: projectConfig.getProperty("versionRoot") ?: "3" val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: "" -val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: "" val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: "" val launcherExe = System.getenv("HMCL_LAUNCHER_EXE") ?: "" @@ -153,7 +152,6 @@ val hmclProperties = buildList { } add("hmcl.version.type" to versionType) add("hmcl.microsoft.auth.id" to microsoftAuthId) - add("hmcl.microsoft.auth.secret" to microsoftAuthSecret) add("hmcl.curseforge.apikey" to curseForgeApiKey) add("hmcl.authlib-injector.version" to libs.authlib.injector.get().version!!) } From 78c9241f31e5dab3239fe1f729a0583a099c6bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Sat, 21 Feb 2026 08:58:29 +0800 Subject: [PATCH 06/17] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20OAuth=20Server=20?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9b911e72b1..cd46359109 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -153,7 +153,7 @@ public OAuth.Session startServer() throws IOException, AuthenticationException { } IOException exception = null; - for (int port : new int[]{29111, 29112, 29113, 29114, 29115}) { + for (int port : new int[]{29712, 29713, 29714, 29715, 29716}) { try { OAuthServer server = new OAuthServer(port); server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true); From e2471431da20efefd04e4b698c75ca3398798fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Sat, 21 Feb 2026 20:34:06 +0800 Subject: [PATCH 07/17] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cd46359109..9b911e72b1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -153,7 +153,7 @@ public OAuth.Session startServer() throws IOException, AuthenticationException { } IOException exception = null; - for (int port : new int[]{29712, 29713, 29714, 29715, 29716}) { + for (int port : new int[]{29111, 29112, 29113, 29114, 29115}) { try { OAuthServer server = new OAuthServer(port); server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true); From 6965e0cbb57a317b0f3a1f2e994baa803e8fdabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:29:06 +0800 Subject: [PATCH 08/17] state --- .../org/jackhuang/hmcl/game/OAuthServer.java | 25 ++++++++++++++++--- .../java/org/jackhuang/hmcl/auth/OAuth.java | 4 +++ 2 files changed, 25 insertions(+), 4 deletions(-) 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 9b911e72b1..0e432b0787 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -30,10 +30,7 @@ import java.io.IOException; import java.security.SecureRandom; -import java.util.Base64; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -46,6 +43,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { private final int port; private final CompletableFuture future = new CompletableFuture<>(); private final String codeVerifier; + private final String state; public static String lastlyOpenedURL; @@ -56,6 +54,15 @@ private OAuthServer(int port) { this.port = port; this.codeVerifier = generateCodeVerifier(); + this.state = generateState(); + } + + // https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 + // https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 + private String generateState() { + byte[] bytes = new byte[32]; + new SecureRandom().nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 @@ -71,6 +78,11 @@ public String getCodeVerifier() { return codeVerifier; } + @Override + public String getState() { + return state; + } + @Override public String getRedirectURI() { return String.format("http://localhost:%d/auth-response", port); @@ -110,6 +122,11 @@ public Response serve(IHTTPSession session) { String parameters = session.getQueryParameterString(); Map query = mapOf(NetworkUtils.parseQuery(parameters)); + + if (query.containsKey("state")) { + if (!Objects.equals(query.get("state"), this.state)) future.completeExceptionally( new AuthenticationException("invalid state")); + } + if (query.containsKey("code")) { idToken = query.get("id_token"); future.complete(query.get("code")); 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 0daf443503..51eec94366 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -83,6 +83,7 @@ private Result authenticateAuthorizationCode(Options options) throws IOException Session session = options.callback.startServer(); String codeVerifier = session.getCodeVerifier(); + String state = session.getState(); String codeChallenge = generateCodeChallenge(codeVerifier); options.callback.openBrowser(GrantFlow.AUTHORIZATION_CODE, NetworkUtils.withQuery(authorizationURL, @@ -92,6 +93,7 @@ private Result authenticateAuthorizationCode(Options options) throws IOException pair("scope", options.scope), pair("prompt", "select_account"), pair("code_challenge", codeChallenge), + pair("state", state), pair("code_challenge_method", "S256") ))); String code = session.waitFor(); @@ -233,6 +235,8 @@ public Options setUserAgent(String userAgent) { } public interface Session { + String getState(); + String getCodeVerifier(); String getRedirectURI(); From 1385ce9b52b1401f6e20c308ad6c8a5d2f5fe462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:42:55 +0800 Subject: [PATCH 09/17] state --- .../org/jackhuang/hmcl/game/OAuthServer.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) 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 0e432b0787..6a6647c499 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -52,25 +52,27 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { private OAuthServer(int port) { super(port); - this.port = port; - this.codeVerifier = generateCodeVerifier(); - this.state = generateState(); - } + SecureRandom random = new SecureRandom(); + String state, codeVerifier; + + { + // https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 + // https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 + byte[] bytes = new byte[32]; + random.nextBytes(bytes); + state = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } - // https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 - // https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 - private String generateState() { - byte[] bytes = new byte[32]; - new SecureRandom().nextBytes(bytes); - return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); - } + { + // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 + byte[] code = new byte[64]; + random.nextBytes(code); + codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(code); + } - // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 - private String generateCodeVerifier() { - SecureRandom sr = new SecureRandom(); - byte[] code = new byte[64]; - sr.nextBytes(code); - return Base64.getUrlEncoder().withoutPadding().encodeToString(code); + this.port = port; + this.codeVerifier = codeVerifier; + this.state = state; } @Override @@ -123,9 +125,10 @@ public Response serve(IHTTPSession session) { Map query = mapOf(NetworkUtils.parseQuery(parameters)); - if (query.containsKey("state")) { - if (!Objects.equals(query.get("state"), this.state)) future.completeExceptionally( new AuthenticationException("invalid state")); - } + if (!(query.containsKey("state"))) { + if (!Objects.equals(query.get("state"), this.state)) + future.completeExceptionally(new AuthenticationException("invalid state")); + } else future.completeExceptionally(new AuthenticationException("missing state")); if (query.containsKey("code")) { idToken = query.get("id_token"); From 3a27464261b714b4da8c5c18be94782ce9e51d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+Ciilu@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:43:45 +0800 Subject: [PATCH 10/17] state --- .../main/java/org/jackhuang/hmcl/game/OAuthServer.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 6a6647c499..92f9ebd6c0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -52,6 +52,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { private OAuthServer(int port) { super(port); + var encoder = Base64.getUrlEncoder().withoutPadding(); SecureRandom random = new SecureRandom(); String state, codeVerifier; @@ -60,14 +61,14 @@ private OAuthServer(int port) { // https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 byte[] bytes = new byte[32]; random.nextBytes(bytes); - state = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + state = encoder.encodeToString(bytes); } { // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 - byte[] code = new byte[64]; - random.nextBytes(code); - codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(code); + byte[] bytes = new byte[64]; + random.nextBytes(bytes); + codeVerifier = encoder.encodeToString(bytes); } this.port = port; From 46063677ea405db6836df6b8c9375fe4e91660be Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 21 Feb 2026 21:50:44 +0800 Subject: [PATCH 11/17] update --- .../org/jackhuang/hmcl/game/OAuthServer.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) 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 92f9ebd6c0..fa6d50c718 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -52,28 +52,25 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session { private OAuthServer(int port) { super(port); + this.port = port; + var encoder = Base64.getUrlEncoder().withoutPadding(); SecureRandom random = new SecureRandom(); - String state, codeVerifier; { // https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 // https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 byte[] bytes = new byte[32]; random.nextBytes(bytes); - state = encoder.encodeToString(bytes); + this.state = encoder.encodeToString(bytes); } { // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 byte[] bytes = new byte[64]; random.nextBytes(bytes); - codeVerifier = encoder.encodeToString(bytes); + this.codeVerifier = encoder.encodeToString(bytes); } - - this.port = port; - this.codeVerifier = codeVerifier; - this.state = state; } @Override @@ -126,10 +123,9 @@ public Response serve(IHTTPSession session) { Map query = mapOf(NetworkUtils.parseQuery(parameters)); - if (!(query.containsKey("state"))) { - if (!Objects.equals(query.get("state"), this.state)) - future.completeExceptionally(new AuthenticationException("invalid state")); - } else future.completeExceptionally(new AuthenticationException("missing state")); + if (!this.state.equals(query.get("state"))) + future.completeExceptionally(new AuthenticationException( + query.containsKey("state") ? "invalid state" : "missing state")); if (query.containsKey("code")) { idToken = query.get("id_token"); From 87111ba6e2154dff08f4a5aa324faf6d091a2dc1 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 21 Feb 2026 21:51:37 +0800 Subject: [PATCH 12/17] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fa6d50c718..51ac1c329f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -55,7 +55,7 @@ private OAuthServer(int port) { this.port = port; var encoder = Base64.getUrlEncoder().withoutPadding(); - SecureRandom random = new SecureRandom(); + var random = new SecureRandom(); { // https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 From a042318b1360033e4354ad1c10b7eb896995b771 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 21 Feb 2026 22:08:41 +0800 Subject: [PATCH 13/17] update --- .../org/jackhuang/hmcl/game/OAuthServer.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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 51ac1c329f..1fd2ad5098 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -123,16 +123,18 @@ public Response serve(IHTTPSession session) { Map query = mapOf(NetworkUtils.parseQuery(parameters)); - if (!this.state.equals(query.get("state"))) - future.completeExceptionally(new AuthenticationException( - query.containsKey("state") ? "invalid state" : "missing state")); - - if (query.containsKey("code")) { - idToken = query.get("id_token"); - future.complete(query.get("code")); + if (this.state.equals(query.get("state"))) { + if (query.containsKey("code")) { + idToken = query.get("id_token"); + future.complete(query.get("code")); + } else { + LOG.warning("Failed to authenticate: missing authorization code in parameters"); + future.completeExceptionally(new AuthenticationException("Failed to authenticate")); + } } else { - LOG.warning("Error: " + parameters); - future.completeExceptionally(new AuthenticationException("failed to authenticate")); + LOG.warning("Failed to authenticate: invalid state"); + future.completeExceptionally(new AuthenticationException( + query.containsKey("state") ? "Invalid state" : "Missing state")); } String html; From e5264e2cc86402edbab29fd564b84c096848ed28 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 21 Feb 2026 22:11:02 +0800 Subject: [PATCH 14/17] update --- .../org/jackhuang/hmcl/game/OAuthServer.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 1fd2ad5098..fb6e69e389 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -123,18 +123,20 @@ public Response serve(IHTTPSession session) { Map query = mapOf(NetworkUtils.parseQuery(parameters)); - if (this.state.equals(query.get("state"))) { - if (query.containsKey("code")) { + if (query.containsKey("code")) { + if (this.state.equals(query.get("state"))) { idToken = query.get("id_token"); future.complete(query.get("code")); + } else if (query.containsKey("state")) { + LOG.warning("Failed to authenticate: missing state in parameters"); + future.completeExceptionally(new AuthenticationException("Missing state")); } else { - LOG.warning("Failed to authenticate: missing authorization code in parameters"); - future.completeExceptionally(new AuthenticationException("Failed to authenticate")); + LOG.warning("Failed to authenticate: invalid state in parameters"); + future.completeExceptionally(new AuthenticationException("Invalid state")); } } else { - LOG.warning("Failed to authenticate: invalid state"); - future.completeExceptionally(new AuthenticationException( - query.containsKey("state") ? "Invalid state" : "Missing state")); + LOG.warning("Failed to authenticate: missing authorization code in parameters"); + future.completeExceptionally(new AuthenticationException("Failed to authenticate")); } String html; From def58f90ad28682ac580b5355e99b9c2ef7db2bc Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 21 Feb 2026 22:12:35 +0800 Subject: [PATCH 15/17] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 fb6e69e389..005461864f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -123,10 +123,11 @@ public Response serve(IHTTPSession session) { Map query = mapOf(NetworkUtils.parseQuery(parameters)); - if (query.containsKey("code")) { + String code = query.get("code"); + if (code != null) { if (this.state.equals(query.get("state"))) { idToken = query.get("id_token"); - future.complete(query.get("code")); + future.complete(code); } else if (query.containsKey("state")) { LOG.warning("Failed to authenticate: missing state in parameters"); future.completeExceptionally(new AuthenticationException("Missing state")); From c7f331242b2fdc45266b9ece02dac9f616015b0b Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 21 Feb 2026 22:13:15 +0800 Subject: [PATCH 16/17] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 005461864f..6bb06b79b3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -130,14 +130,14 @@ public Response serve(IHTTPSession session) { future.complete(code); } else if (query.containsKey("state")) { LOG.warning("Failed to authenticate: missing state in parameters"); - future.completeExceptionally(new AuthenticationException("Missing state")); + future.completeExceptionally(new AuthenticationException("Failed to authenticate: missing state")); } else { LOG.warning("Failed to authenticate: invalid state in parameters"); - future.completeExceptionally(new AuthenticationException("Invalid state")); + future.completeExceptionally(new AuthenticationException("Failed to authenticate: invalid state")); } } else { LOG.warning("Failed to authenticate: missing authorization code in parameters"); - future.completeExceptionally(new AuthenticationException("Failed to authenticate")); + future.completeExceptionally(new AuthenticationException("Failed to authenticate: missing authorization code")); } String html; From 62e906b5e238e0e9df1f5576b0bce835ede43d69 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 21 Feb 2026 22:21:53 +0800 Subject: [PATCH 17/17] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 6bb06b79b3..726494e38f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -129,11 +129,11 @@ public Response serve(IHTTPSession session) { idToken = query.get("id_token"); future.complete(code); } else if (query.containsKey("state")) { - LOG.warning("Failed to authenticate: missing state in parameters"); - future.completeExceptionally(new AuthenticationException("Failed to authenticate: missing state")); - } else { LOG.warning("Failed to authenticate: invalid state in parameters"); future.completeExceptionally(new AuthenticationException("Failed to authenticate: invalid state")); + } else { + LOG.warning("Failed to authenticate: missing state in parameters"); + future.completeExceptionally(new AuthenticationException("Failed to authenticate: missing state")); } } else { LOG.warning("Failed to authenticate: missing authorization code in parameters");