Add MS Java access-token & cookies auth services#950
Add MS Java access-token & cookies auth services#950FoxYolk wants to merge 1 commit intoAlexProgrammerDE:mainfrom
Conversation
Add support for importing Microsoft Java accounts via raw Minecraft access tokens and browser cookies. Introduces MSJavaAccessTokenAuthService (access-token-only auth, non-refreshable) and MSJavaCookiesAuthService (exchanges login.live.com cookies for a refresh token), and extends MSJavaRefreshTokenAuthService with cookie-import helpers/validation. Update MCAuthService to include new service variants and conversion logic, and extend AuthType/proto enums to expose MICROSOFT_JAVA_ACCESS_TOKEN and MICROSOFT_JAVA_COOKIES. OnlineChainJavaData gains helpers to detect/access access-token-only accounts. InstanceManager, SFSessionService and BotConnection are adjusted to handle non-refreshable access-token accounts and to skip expired auth. Also: javadoc Gradle task fixes (classpath + delombok ordering and configuration cache note), .gitignore additions for local artifacts, add start.bat helper, and include a bundled JavaAuthManager.class binary. Protobuf messages updated for new credential/service types.
There was a problem hiding this comment.
Pull request overview
This PR adds new Microsoft Java Edition authentication import paths—supporting raw Minecraft access tokens and browser cookie imports—and updates server/runtime logic to handle non-refreshable accounts and skip expired credentials.
Changes:
- Introduces
MSJavaAccessTokenAuthService(non-refreshable) andMSJavaCookiesAuthService(cookie-to-refresh-token exchange), plus enum/proto exposure for new credential types. - Updates session/join + instance lifecycle to use a unified “get access token” helper and to skip/avoid expired non-refreshable accounts.
- Adjusts Javadoc Gradle task wiring and updates
.gitignore/startup helpers; also adds several workspace/binary artifacts to the repo.
Reviewed changes
Copilot reviewed 15 out of 19 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| untracked8.txt | Adds a local workspace artifact list (should not be committed). |
| untracked.txt | Same as above but with encoding corruption (should not be committed). |
| builder.txt | Appears to be a generated bytecode/javap dump (should not be committed). |
| start.bat | Windows helper to build/find and run the dedicated launcher jar. |
| .gitignore | Ignores downloaded libs, local runtime data, sqlite DBs, and secrets. |
| proto/src/main/proto/soulfire/mc-auth.proto | Documents cookie-import payload for credentials auth request. |
| proto/src/main/proto/soulfire/common.proto | Adds new credential enum values + access-token account type. |
| mod/src/main/java/com/soulfiremc/server/grpc/MCAuthServiceImpl.java | Rejects UNRECOGNIZED auth enums to catch version mismatches early. |
| mod/src/main/java/com/soulfiremc/server/bot/SFSessionService.java | Uses service-based expiry check and access-token retrieval for join. |
| mod/src/main/java/com/soulfiremc/server/bot/BotConnection.java | Uses OnlineChainJavaData.getAccessToken(...) for Minecraft copy creation. |
| mod/src/main/java/com/soulfiremc/server/account/service/OnlineChainJavaData.java | Adds access-token-only mode + access-token retrieval helper. |
| mod/src/main/java/com/soulfiremc/server/account/MSJavaRefreshTokenAuthService.java | Adds refresh-token input validation and (currently unused) cookie helpers. |
| mod/src/main/java/com/soulfiremc/server/account/MSJavaCookiesAuthService.java | New cookie import service exchanging cookies for a refresh token. |
| mod/src/main/java/com/soulfiremc/server/account/MSJavaAccessTokenAuthService.java | New access-token-only auth service and expiry detection via JWT exp. |
| mod/src/main/java/com/soulfiremc/server/account/MCAuthService.java | Adds new service variants to sealed interface + conversion logic. |
| mod/src/main/java/com/soulfiremc/server/account/AuthType.java | Adds MICROSOFT_JAVA_ACCESS_TOKEN auth type. |
| mod/src/main/java/com/soulfiremc/server/InstanceManager.java | Handles non-refreshable accounts and skips expired non-refreshable auth. |
| javadoc/build.gradle.kts | Updates aggregate Javadoc deps/classpath and delombok ordering; disables config cache. |
| net/raphimc/minecraftauth/java/JavaAuthManager.class | Adds a compiled .class into the repo (problematic). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Override | ||
| public CompletableFuture<MinecraftAccount> login(MSJavaAccessTokenAuthData data, @Nullable SFProxy proxyData, Executor executor) { | ||
| return CompletableFuture.supplyAsync(() -> { | ||
| try { | ||
| var profileJson = fetchProfile(data.accessToken); |
There was a problem hiding this comment.
proxyData is accepted by login(...) but never used (the profile request uses a raw HttpURLConnection). This breaks the existing "use proxies for account auth" behavior for access-token imports; consider using the project’s HTTP helpers (e.g., ReactorHttpHelper/LenniHttpHelper) so proxy settings and shared defaults are honored.
| @Override | |
| public CompletableFuture<MinecraftAccount> login(MSJavaAccessTokenAuthData data, @Nullable SFProxy proxyData, Executor executor) { | |
| return CompletableFuture.supplyAsync(() -> { | |
| try { | |
| var profileJson = fetchProfile(data.accessToken); | |
| private JsonObject fetchProfile(String accessToken, @Nullable SFProxy proxyData) throws Exception { | |
| // Delegate to the existing implementation for now; proxyData is threaded through | |
| // so that proxy-aware logic can be implemented here without changing callers. | |
| return fetchProfile(accessToken); | |
| } | |
| @Override | |
| public CompletableFuture<MinecraftAccount> login(MSJavaAccessTokenAuthData data, @Nullable SFProxy proxyData, Executor executor) { | |
| return CompletableFuture.supplyAsync(() -> { | |
| try { | |
| var profileJson = fetchProfile(data.accessToken, proxyData); |
| var statusCode = connection.getResponseCode(); | ||
| if (statusCode != 200) { | ||
| // Try to read error body for better diagnostics | ||
| var errorStream = connection.getErrorStream(); | ||
| var errorBody = ""; | ||
| if (errorStream != null) { | ||
| try (var reader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) { | ||
| errorBody = reader.lines().collect(Collectors.joining("\n")); | ||
| } | ||
| } | ||
| throw new IllegalStateException("Failed to fetch Minecraft profile: HTTP " + statusCode + " " + errorBody); | ||
| } | ||
|
|
||
| try (var reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { | ||
| var body = reader.lines().collect(Collectors.joining("\n")); | ||
| return GsonInstance.GSON.fromJson(body, JsonObject.class); |
There was a problem hiding this comment.
connection.disconnect() is only executed on the success path; if statusCode != 200 this method throws before reaching the finally, leaving the HttpURLConnection open. Wrap the whole request/response handling in a try/finally (or disconnect in the error branch) so the connection is always cleaned up.
| var statusCode = connection.getResponseCode(); | |
| if (statusCode != 200) { | |
| // Try to read error body for better diagnostics | |
| var errorStream = connection.getErrorStream(); | |
| var errorBody = ""; | |
| if (errorStream != null) { | |
| try (var reader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) { | |
| errorBody = reader.lines().collect(Collectors.joining("\n")); | |
| } | |
| } | |
| throw new IllegalStateException("Failed to fetch Minecraft profile: HTTP " + statusCode + " " + errorBody); | |
| } | |
| try (var reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { | |
| var body = reader.lines().collect(Collectors.joining("\n")); | |
| return GsonInstance.GSON.fromJson(body, JsonObject.class); | |
| try { | |
| var statusCode = connection.getResponseCode(); | |
| if (statusCode != 200) { | |
| // Try to read error body for better diagnostics | |
| var errorStream = connection.getErrorStream(); | |
| var errorBody = ""; | |
| if (errorStream != null) { | |
| try (var reader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) { | |
| errorBody = reader.lines().collect(Collectors.joining("\n")); | |
| } | |
| } | |
| throw new IllegalStateException("Failed to fetch Minecraft profile: HTTP " + statusCode + " " + errorBody); | |
| } | |
| try (var reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { | |
| var body = reader.lines().collect(Collectors.joining("\n")); | |
| return GsonInstance.GSON.fromJson(body, JsonObject.class); | |
| } |
|
|
||
| @Override | ||
| public MSJavaAccessTokenAuthData createData(String data) { | ||
| return new MSJavaAccessTokenAuthData(data.strip()); |
There was a problem hiding this comment.
createData accepts an empty/blank access token (after strip()), which will later fail with a less actionable HTTP error. Consider validating non-empty input (and possibly rejecting obvious cookie/refresh-token formats) to give callers a clear IllegalArgumentException early.
| return new MSJavaAccessTokenAuthData(data.strip()); | |
| var stripped = data.strip(); | |
| if (stripped.isEmpty()) { | |
| throw new IllegalArgumentException("Access token must not be empty or blank"); | |
| } | |
| return new MSJavaAccessTokenAuthData(stripped); |
| private static String exchangeCookieInputForRefreshToken(String cookieInput, @Nullable SFProxy proxyData) { | ||
| var cookieHeader = cookieInputToCookieHeader(cookieInput); | ||
| var authorizeUri = URI.create( | ||
| "https://login.live.com/oauth20_authorize.srf" | ||
| + "?client_id=" + urlEncode(CLIENT_ID) | ||
| + "&response_type=code" | ||
| + "&response_mode=query" | ||
| + "&redirect_uri=" + urlEncode(REDIRECT_URI) | ||
| + "&scope=" + urlEncode(SCOPE) | ||
| + "&prompt=none" | ||
| ); | ||
|
|
||
| var location = ReactorHttpHelper.createReactorClient(proxyData, false) |
There was a problem hiding this comment.
exchangeCookieInputForRefreshToken(...) (and the associated cookie parsing/exchange helpers below) is currently unused in this class (only declared, never called). If cookie-import is meant to be supported via MSJavaCookiesAuthService, consider removing this dead code or extracting shared cookie parsing/exchange logic into a reusable utility to avoid duplication.
| config/spark/tmp-client/about.txt | ||
| libraries/HdrHistogram-2.2.2.jar | ||
| libraries/HikariCP-7.0.2.jar | ||
| libraries/Java-WebSocket-1.6.0.jar | ||
| libraries/LambdaEvents-2.4.2.jar |
There was a problem hiding this comment.
These look like a local git status/workspace artifact list (including paths like secret-key.bin, downloaded jars, etc.) and shouldn’t be committed to the repo. Please remove this file from the PR and rely on .gitignore for ignoring local artifacts.
|
Thanks for the PR. I've reimplemented this now manuyll. |
Add support for importing Microsoft Java accounts via raw Minecraft access tokens and browser cookies. Introduces MSJavaAccessTokenAuthService (access-token-only auth, non-refreshable) and MSJavaCookiesAuthService (exchanges login.live.com cookies for a refresh token), and extends MSJavaRefreshTokenAuthService with cookie-import helpers/validation. Update MCAuthService to include new service variants and conversion logic, and extend AuthType/proto enums to expose MICROSOFT_JAVA_ACCESS_TOKEN and MICROSOFT_JAVA_COOKIES. OnlineChainJavaData gains helpers to detect/access access-token-only accounts. InstanceManager, SFSessionService and BotConnection are adjusted to handle non-refreshable access-token accounts and to skip expired auth. Also: javadoc Gradle task fixes (classpath + delombok ordering and configuration cache note), .gitignore additions for local artifacts, add start.bat helper, and include a bundled JavaAuthManager.class binary. Protobuf messages updated for new credential/service types.