From 943355c1d442bba49d79620097c6d88ba34f9a5a Mon Sep 17 00:00:00 2001 From: Ap4uuk Date: Mon, 1 Jun 2026 21:30:29 +0300 Subject: [PATCH 1/5] [FIX] Validate profile token restore and join check profile binding --- .../request/SetProfileRequestEvent.java | 30 ++- .../config/LaunchServerConfig.java | 1 + .../launchserver/manangers/AuthManager.java | 176 +++++++++++++++++- .../socket/response/auth/RestoreResponse.java | 2 + .../response/auth/SetProfileResponse.java | 4 +- 5 files changed, 208 insertions(+), 5 deletions(-) diff --git a/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java b/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java index 320862dc5..c81b8bc87 100644 --- a/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java +++ b/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java @@ -1,32 +1,56 @@ package pro.gravit.launcher.base.events.request; import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.base.events.ExtendedTokenRequestEvent; import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.profiles.ClientProfile; import java.util.UUID; -public class SetProfileRequestEvent extends RequestEvent { +public class SetProfileRequestEvent extends RequestEvent implements ExtendedTokenRequestEvent { + public static final String CLIENT_PROFILE_EXTENDED_TOKEN_NAME = "clientProfile"; @SuppressWarnings("unused") private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541"); @LauncherNetworkAPI public final ClientProfile newProfile; @LauncherNetworkAPI public final String tag; + public final String profileExtendedToken; + public final long profileExtendedTokenExpire; public SetProfileRequestEvent(ClientProfile newProfile) { - this.newProfile = newProfile; - this.tag = null; + this(newProfile, null, null, 0); } public SetProfileRequestEvent(ClientProfile newProfile, String tag) { + this(newProfile, tag, null, 0); + } + + public SetProfileRequestEvent(ClientProfile newProfile, String tag, String profileExtendedToken, long profileExtendedTokenExpire) { this.newProfile = newProfile; this.tag = tag; + this.profileExtendedToken = profileExtendedToken; + this.profileExtendedTokenExpire = profileExtendedTokenExpire; } @Override public String getType() { return "setProfile"; } + + @Override + public String getExtendedTokenName() { + return CLIENT_PROFILE_EXTENDED_TOKEN_NAME; + } + + @Override + public String getExtendedToken() { + return profileExtendedToken; + } + + @Override + public long getExtendedTokenExpire() { + return profileExtendedTokenExpire; + } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java b/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java index 3fe7b59b9..cad743dcd 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java @@ -294,5 +294,6 @@ public static class NettySecurityConfig { public long publicKeyTokenExpire = HOURS.toSeconds(8); public long launcherTokenExpire = HOURS.toSeconds(8); + public long joinServerTimeoutMillis = 10_000; } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java index 085c82a30..c1dca2492 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java @@ -6,6 +6,7 @@ import org.apache.logging.log4j.Logger; import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.events.request.AuthRequestEvent; +import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.PlayerProfile; import pro.gravit.launcher.base.request.auth.AuthRequest; @@ -20,6 +21,7 @@ import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.auth.AuthResponse; @@ -29,12 +31,23 @@ import javax.crypto.Cipher; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class AuthManager { + public static final String WRONG_CLIENT_ERROR_MESSAGE = "Wrong Client"; + public static final String AUTH_BACKEND_TIMEOUT_ERROR_MESSAGE = "Auth backend timeout"; private transient final LaunchServer server; private transient final Logger logger = LogManager.getLogger(); private transient final JwtParser checkServerTokenParser; + private transient final JwtParser clientProfileTokenParser; + private final transient Map joinServerProfilesByServerId = new ConcurrentHashMap<>(); public AuthManager(LaunchServer server) { this.server = server; @@ -43,6 +56,11 @@ public AuthManager(LaunchServer server) { .require("tokenType", "checkServer") .verifyWith(server.keyAgreementManager.ecdsaPublicKey) .build(); + this.clientProfileTokenParser = Jwts.parser() + .requireIssuer("LaunchServer") + .require("tokenType", SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME) + .verifyWith(server.keyAgreementManager.ecdsaPublicKey) + .build(); } public String newCheckServerToken(String serverName, String authId, boolean publicOnly) { @@ -66,6 +84,34 @@ public CheckServerTokenInfo parseCheckServerToken(String token) { } } + public String newClientProfileToken(UUID profileUUID, String profileTag, String authId) { + var builder = Jwts.builder() + .issuer("LaunchServer") + .claim("tokenType", SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME) + .claim("profileUUID", profileUUID.toString()); + if (profileTag != null) { + builder = builder.claim("profileTag", profileTag); + } + if (authId != null) { + builder = builder.claim("authId", authId); + } + return builder.setExpiration(Date.from(LocalDateTime.now().plusSeconds(server.config.netty.security.launcherTokenExpire).toInstant(ZoneOffset.UTC))) + .signWith(server.keyAgreementManager.ecdsaPrivateKey) + .compact(); + } + + public ClientProfileTokenInfo parseClientProfileToken(String token) { + try { + var jwt = clientProfileTokenParser.parseClaimsJws(token).getBody(); + return new ClientProfileTokenInfo( + UUID.fromString(jwt.get("profileUUID", String.class)), + jwt.get("profileTag", String.class), + jwt.get("authId", String.class)); + } catch (Exception e) { + return null; + } + } + /** * Create AuthContext * @@ -168,17 +214,108 @@ public CheckServerReport checkServer(Client client, String username, String serv if(supportExtended != null) { var session = supportExtended.extendedCheckServer(client, username, serverID); if(session == null) return null; + if(!isCheckServerProfileAllowed(client, serverID)) { + throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); + } return CheckServerReport.ofUserSession(session, getPlayerProfile(client.auth, session.getUser())); } else { var user = client.auth.core.checkServer(client, username, serverID); if (user == null) return null; + if(!isCheckServerProfileAllowed(client, serverID)) { + throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); + } return CheckServerReport.ofUser(user, getPlayerProfile(client.auth, user)); } } public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException { if (client.auth == null) return false; - return client.auth.core.joinServer(client, username, uuid, accessToken, serverID); + UUID profileUUID = resolveCurrentClientProfileUUID(client); + if(client.type == AuthResponse.ConnectTypes.CLIENT && profileUUID == null) { + logger.warn("joinServer denied: profile is not selected/restored for user {} (serverID={})", + username != null ? username : uuid, serverID); + return false; + } + long joinServerTimeoutMillis = server.config.netty.security.joinServerTimeoutMillis; + boolean result; + if (joinServerTimeoutMillis <= 0) { + result = client.auth.core.joinServer(client, username, uuid, accessToken, serverID); + } else { + var joinServerFuture = CompletableFuture.supplyAsync(() -> { + try { + return client.auth.core.joinServer(client, username, uuid, accessToken, serverID); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + try { + result = joinServerFuture.get(joinServerTimeoutMillis, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + joinServerFuture.cancel(true); + logger.warn("joinServer timed out after {} ms for user {} (serverID={})", + joinServerTimeoutMillis, username != null ? username : uuid, serverID); + throw new AuthException(AUTH_BACKEND_TIMEOUT_ERROR_MESSAGE); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("joinServer interrupted", e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException runtimeException && runtimeException.getCause() instanceof IOException ioException) { + throw ioException; + } + if (cause instanceof IOException ioException) { + throw ioException; + } + throw new IOException("joinServer failed", cause); + } + } + if(result && serverID != null && profileUUID != null) { + joinServerProfilesByServerId.put(serverID, profileUUID); + } + return result; + } + + private boolean isCheckServerProfileAllowed(Client client, String serverID) { + if(serverID == null) { + return true; + } + UUID expectedProfileUUID = joinServerProfilesByServerId.remove(serverID); + if(expectedProfileUUID == null) { + UUID currentProfileUUID = resolveCurrentClientProfileUUID(client); + if(currentProfileUUID == null) { + return true; + } + logger.warn("checkServer denied: no joinServer profile context for serverID={}, but server profile is {}", + serverID, currentProfileUUID); + return false; + } + UUID currentProfileUUID = resolveCurrentClientProfileUUID(client); + if(currentProfileUUID == null) { + logger.warn("checkServer denied: server profile is not resolved, expected profile {} for serverID={}", + expectedProfileUUID, serverID); + return false; + } + if(!currentProfileUUID.equals(expectedProfileUUID)) { + logger.warn("checkServer denied: profile mismatch for serverID={} (expected={}, current={})", + serverID, expectedProfileUUID, currentProfileUUID); + return false; + } + return true; + } + + private UUID resolveCurrentClientProfileUUID(Client client) { + if(client.profile != null) { + return client.profile.getUuid(); + } + String serverName = client.getProperty("launchserver.serverName"); + if(serverName == null) { + return null; + } + try { + return UUID.fromString(serverName); + } catch (IllegalArgumentException e) { + return null; + } } public PlayerProfile getPlayerProfile(Client client) { @@ -298,6 +435,9 @@ private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.Au public record CheckServerTokenInfo(String serverName, String authId, boolean isPublic) { } + public record ClientProfileTokenInfo(UUID profileUUID, String profileTag, String authId) { + } + public static class CheckServerVerifier implements RestoreResponse.ExtendedTokenProvider { private final LaunchServer server; @@ -319,11 +459,45 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken client.permissions.addPerm("launchserver.checkserver.extended"); client.permissions.addPerm("launchserver.profile.%s.show".formatted(info.serverName)); } + try { + UUID profileUUID = UUID.fromString(info.serverName); + ProfilesProvider.CompletedProfile profile = server.config.profilesProvider.get(profileUUID, null); + if(profile != null) { + client.profile = profile; + } + } catch (IllegalArgumentException ignored) { + // serverName may be a custom value, profile restore from this token is optional + } client.setProperty("launchserver.serverName", info.serverName); return true; } } + public static class ClientProfileTokenVerifier implements RestoreResponse.ExtendedTokenProvider { + private final LaunchServer server; + + public ClientProfileTokenVerifier(LaunchServer server) { + this.server = server; + } + + @Override + public boolean accept(Client client, AuthProviderPair pair, String extendedToken) { + var info = server.authManager.parseClientProfileToken(extendedToken); + if (info == null) { + return false; + } + if(info.authId() != null && pair != null && !info.authId().equals(pair.name)) { + return false; + } + ProfilesProvider.CompletedProfile profile = server.config.profilesProvider.get(info.profileUUID(), info.profileTag()); + if (profile == null) { + return false; + } + client.profile = profile; + return true; + } + } + public static class CheckServerReport { public UUID uuid; public User user; diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java index 9412aeb3e..5e724f998 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java @@ -4,6 +4,7 @@ import pro.gravit.launcher.base.events.request.AuthRequestEvent; import pro.gravit.launcher.base.events.request.LauncherRequestEvent; import pro.gravit.launcher.base.events.request.RestoreRequestEvent; +import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.core.AuthCoreProvider; @@ -34,6 +35,7 @@ public static void registerProviders(LaunchServer server) { providers.put("publicKey", new AdvancedProtectHandler.PublicKeyTokenVerifier(server)); providers.put("hardware", new AdvancedProtectHandler.HardwareInfoTokenVerifier(server)); providers.put("checkServer", new AuthManager.CheckServerVerifier(server)); + providers.put(SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME, new AuthManager.ClientProfileTokenVerifier(server)); registeredProviders = true; } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java index 814a6c28c..ff9745441 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java @@ -30,7 +30,9 @@ public void execute(ChannelHandlerContext ctx, Client client) { return; } client.profile = profile; - sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag())); + sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag(), + server.authManager.newClientProfileToken(profile.getUuid(), profile.getTag(), client.auth_id), + server.config.netty.security.launcherTokenExpire * 1000)); } @Override From 3f1fcedae40d909f51798a7ca2416ca1bdc6baad Mon Sep 17 00:00:00 2001 From: Ap4uuk Date: Mon, 1 Jun 2026 21:30:29 +0300 Subject: [PATCH 2/5] [FIX] Validate profile token restore and join check profile binding --- .../request/SetProfileRequestEvent.java | 30 ++- .../launchserver/manangers/AuthManager.java | 176 +++++++++++++++++- .../socket/response/auth/RestoreResponse.java | 2 + .../response/auth/SetProfileResponse.java | 4 +- 4 files changed, 207 insertions(+), 5 deletions(-) diff --git a/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java b/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java index 320862dc5..c81b8bc87 100644 --- a/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java +++ b/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java @@ -1,32 +1,56 @@ package pro.gravit.launcher.base.events.request; import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.base.events.ExtendedTokenRequestEvent; import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.profiles.ClientProfile; import java.util.UUID; -public class SetProfileRequestEvent extends RequestEvent { +public class SetProfileRequestEvent extends RequestEvent implements ExtendedTokenRequestEvent { + public static final String CLIENT_PROFILE_EXTENDED_TOKEN_NAME = "clientProfile"; @SuppressWarnings("unused") private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541"); @LauncherNetworkAPI public final ClientProfile newProfile; @LauncherNetworkAPI public final String tag; + public final String profileExtendedToken; + public final long profileExtendedTokenExpire; public SetProfileRequestEvent(ClientProfile newProfile) { - this.newProfile = newProfile; - this.tag = null; + this(newProfile, null, null, 0); } public SetProfileRequestEvent(ClientProfile newProfile, String tag) { + this(newProfile, tag, null, 0); + } + + public SetProfileRequestEvent(ClientProfile newProfile, String tag, String profileExtendedToken, long profileExtendedTokenExpire) { this.newProfile = newProfile; this.tag = tag; + this.profileExtendedToken = profileExtendedToken; + this.profileExtendedTokenExpire = profileExtendedTokenExpire; } @Override public String getType() { return "setProfile"; } + + @Override + public String getExtendedTokenName() { + return CLIENT_PROFILE_EXTENDED_TOKEN_NAME; + } + + @Override + public String getExtendedToken() { + return profileExtendedToken; + } + + @Override + public long getExtendedTokenExpire() { + return profileExtendedTokenExpire; + } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java index 085c82a30..c1dca2492 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java @@ -6,6 +6,7 @@ import org.apache.logging.log4j.Logger; import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.events.request.AuthRequestEvent; +import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.PlayerProfile; import pro.gravit.launcher.base.request.auth.AuthRequest; @@ -20,6 +21,7 @@ import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.auth.AuthResponse; @@ -29,12 +31,23 @@ import javax.crypto.Cipher; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public class AuthManager { + public static final String WRONG_CLIENT_ERROR_MESSAGE = "Wrong Client"; + public static final String AUTH_BACKEND_TIMEOUT_ERROR_MESSAGE = "Auth backend timeout"; private transient final LaunchServer server; private transient final Logger logger = LogManager.getLogger(); private transient final JwtParser checkServerTokenParser; + private transient final JwtParser clientProfileTokenParser; + private final transient Map joinServerProfilesByServerId = new ConcurrentHashMap<>(); public AuthManager(LaunchServer server) { this.server = server; @@ -43,6 +56,11 @@ public AuthManager(LaunchServer server) { .require("tokenType", "checkServer") .verifyWith(server.keyAgreementManager.ecdsaPublicKey) .build(); + this.clientProfileTokenParser = Jwts.parser() + .requireIssuer("LaunchServer") + .require("tokenType", SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME) + .verifyWith(server.keyAgreementManager.ecdsaPublicKey) + .build(); } public String newCheckServerToken(String serverName, String authId, boolean publicOnly) { @@ -66,6 +84,34 @@ public CheckServerTokenInfo parseCheckServerToken(String token) { } } + public String newClientProfileToken(UUID profileUUID, String profileTag, String authId) { + var builder = Jwts.builder() + .issuer("LaunchServer") + .claim("tokenType", SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME) + .claim("profileUUID", profileUUID.toString()); + if (profileTag != null) { + builder = builder.claim("profileTag", profileTag); + } + if (authId != null) { + builder = builder.claim("authId", authId); + } + return builder.setExpiration(Date.from(LocalDateTime.now().plusSeconds(server.config.netty.security.launcherTokenExpire).toInstant(ZoneOffset.UTC))) + .signWith(server.keyAgreementManager.ecdsaPrivateKey) + .compact(); + } + + public ClientProfileTokenInfo parseClientProfileToken(String token) { + try { + var jwt = clientProfileTokenParser.parseClaimsJws(token).getBody(); + return new ClientProfileTokenInfo( + UUID.fromString(jwt.get("profileUUID", String.class)), + jwt.get("profileTag", String.class), + jwt.get("authId", String.class)); + } catch (Exception e) { + return null; + } + } + /** * Create AuthContext * @@ -168,17 +214,108 @@ public CheckServerReport checkServer(Client client, String username, String serv if(supportExtended != null) { var session = supportExtended.extendedCheckServer(client, username, serverID); if(session == null) return null; + if(!isCheckServerProfileAllowed(client, serverID)) { + throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); + } return CheckServerReport.ofUserSession(session, getPlayerProfile(client.auth, session.getUser())); } else { var user = client.auth.core.checkServer(client, username, serverID); if (user == null) return null; + if(!isCheckServerProfileAllowed(client, serverID)) { + throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); + } return CheckServerReport.ofUser(user, getPlayerProfile(client.auth, user)); } } public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException { if (client.auth == null) return false; - return client.auth.core.joinServer(client, username, uuid, accessToken, serverID); + UUID profileUUID = resolveCurrentClientProfileUUID(client); + if(client.type == AuthResponse.ConnectTypes.CLIENT && profileUUID == null) { + logger.warn("joinServer denied: profile is not selected/restored for user {} (serverID={})", + username != null ? username : uuid, serverID); + return false; + } + long joinServerTimeoutMillis = server.config.netty.security.joinServerTimeoutMillis; + boolean result; + if (joinServerTimeoutMillis <= 0) { + result = client.auth.core.joinServer(client, username, uuid, accessToken, serverID); + } else { + var joinServerFuture = CompletableFuture.supplyAsync(() -> { + try { + return client.auth.core.joinServer(client, username, uuid, accessToken, serverID); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + try { + result = joinServerFuture.get(joinServerTimeoutMillis, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + joinServerFuture.cancel(true); + logger.warn("joinServer timed out after {} ms for user {} (serverID={})", + joinServerTimeoutMillis, username != null ? username : uuid, serverID); + throw new AuthException(AUTH_BACKEND_TIMEOUT_ERROR_MESSAGE); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("joinServer interrupted", e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException runtimeException && runtimeException.getCause() instanceof IOException ioException) { + throw ioException; + } + if (cause instanceof IOException ioException) { + throw ioException; + } + throw new IOException("joinServer failed", cause); + } + } + if(result && serverID != null && profileUUID != null) { + joinServerProfilesByServerId.put(serverID, profileUUID); + } + return result; + } + + private boolean isCheckServerProfileAllowed(Client client, String serverID) { + if(serverID == null) { + return true; + } + UUID expectedProfileUUID = joinServerProfilesByServerId.remove(serverID); + if(expectedProfileUUID == null) { + UUID currentProfileUUID = resolveCurrentClientProfileUUID(client); + if(currentProfileUUID == null) { + return true; + } + logger.warn("checkServer denied: no joinServer profile context for serverID={}, but server profile is {}", + serverID, currentProfileUUID); + return false; + } + UUID currentProfileUUID = resolveCurrentClientProfileUUID(client); + if(currentProfileUUID == null) { + logger.warn("checkServer denied: server profile is not resolved, expected profile {} for serverID={}", + expectedProfileUUID, serverID); + return false; + } + if(!currentProfileUUID.equals(expectedProfileUUID)) { + logger.warn("checkServer denied: profile mismatch for serverID={} (expected={}, current={})", + serverID, expectedProfileUUID, currentProfileUUID); + return false; + } + return true; + } + + private UUID resolveCurrentClientProfileUUID(Client client) { + if(client.profile != null) { + return client.profile.getUuid(); + } + String serverName = client.getProperty("launchserver.serverName"); + if(serverName == null) { + return null; + } + try { + return UUID.fromString(serverName); + } catch (IllegalArgumentException e) { + return null; + } } public PlayerProfile getPlayerProfile(Client client) { @@ -298,6 +435,9 @@ private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.Au public record CheckServerTokenInfo(String serverName, String authId, boolean isPublic) { } + public record ClientProfileTokenInfo(UUID profileUUID, String profileTag, String authId) { + } + public static class CheckServerVerifier implements RestoreResponse.ExtendedTokenProvider { private final LaunchServer server; @@ -319,11 +459,45 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken client.permissions.addPerm("launchserver.checkserver.extended"); client.permissions.addPerm("launchserver.profile.%s.show".formatted(info.serverName)); } + try { + UUID profileUUID = UUID.fromString(info.serverName); + ProfilesProvider.CompletedProfile profile = server.config.profilesProvider.get(profileUUID, null); + if(profile != null) { + client.profile = profile; + } + } catch (IllegalArgumentException ignored) { + // serverName may be a custom value, profile restore from this token is optional + } client.setProperty("launchserver.serverName", info.serverName); return true; } } + public static class ClientProfileTokenVerifier implements RestoreResponse.ExtendedTokenProvider { + private final LaunchServer server; + + public ClientProfileTokenVerifier(LaunchServer server) { + this.server = server; + } + + @Override + public boolean accept(Client client, AuthProviderPair pair, String extendedToken) { + var info = server.authManager.parseClientProfileToken(extendedToken); + if (info == null) { + return false; + } + if(info.authId() != null && pair != null && !info.authId().equals(pair.name)) { + return false; + } + ProfilesProvider.CompletedProfile profile = server.config.profilesProvider.get(info.profileUUID(), info.profileTag()); + if (profile == null) { + return false; + } + client.profile = profile; + return true; + } + } + public static class CheckServerReport { public UUID uuid; public User user; diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java index 9412aeb3e..5e724f998 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java @@ -4,6 +4,7 @@ import pro.gravit.launcher.base.events.request.AuthRequestEvent; import pro.gravit.launcher.base.events.request.LauncherRequestEvent; import pro.gravit.launcher.base.events.request.RestoreRequestEvent; +import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.core.AuthCoreProvider; @@ -34,6 +35,7 @@ public static void registerProviders(LaunchServer server) { providers.put("publicKey", new AdvancedProtectHandler.PublicKeyTokenVerifier(server)); providers.put("hardware", new AdvancedProtectHandler.HardwareInfoTokenVerifier(server)); providers.put("checkServer", new AuthManager.CheckServerVerifier(server)); + providers.put(SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME, new AuthManager.ClientProfileTokenVerifier(server)); registeredProviders = true; } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java index 814a6c28c..ff9745441 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java @@ -30,7 +30,9 @@ public void execute(ChannelHandlerContext ctx, Client client) { return; } client.profile = profile; - sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag())); + sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag(), + server.authManager.newClientProfileToken(profile.getUuid(), profile.getTag(), client.auth_id), + server.config.netty.security.launcherTokenExpire * 1000)); } @Override From 6ccef7b9dd1bad5e54bfb1d926e7639479217e7b Mon Sep 17 00:00:00 2001 From: Ap4uuk Date: Mon, 1 Jun 2026 22:29:01 +0300 Subject: [PATCH 3/5] [FIX] Preserve manual disable state for mark optionals --- .../runtime/backend/ProfileSettingsImpl.java | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java b/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java index cabbb5b5a..1e5d57cb2 100644 --- a/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java +++ b/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java @@ -26,6 +26,8 @@ public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSett @LauncherNetworkAPI private Set enabled; @LauncherNetworkAPI + private Set disabled; + @LauncherNetworkAPI private String saveJavaPath; transient OptionalView view; transient volatile JavaHelper.JavaVersion selectedJava; @@ -184,6 +186,7 @@ public ProfileSettingsImpl copy() { cloned.ram = new HashMap<>(ram); cloned.flags = new HashSet<>(flags); cloned.enabled = new HashSet<>(enabled); + cloned.disabled = disabled == null ? null : new HashSet<>(disabled); if(view != null) { cloned.view = new OptionalView(profile, view); } @@ -194,9 +197,15 @@ public ProfileSettingsImpl copy() { public void updateEnabledMods() { enabled = new HashSet<>(); + disabled = new HashSet<>(); for(var e : view.enabled) { enabled.add(e.name); } + for (var e : view.all) { + if (e.mark && !view.isEnabled(e)) { + disabled.add(e.name); + } + } if(selectedJava != null) { saveJavaPath = selectedJava.getPath().toAbsolutePath().toString(); } @@ -207,12 +216,23 @@ public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { this.profile = profile; this.view = new OptionalView(profile); processTriggers(profile, this.view); - for(var e : enabled) { - var opt = profile.getOptionalFile(e); - if(opt == null) { - continue; + if (disabled != null) { + for (var e : disabled) { + var opt = profile.getOptionalFile(e); + if (opt == null) { + continue; + } + disableOptional(opt, (var1, var2) -> {}); + } + } + if (enabled != null) { + for(var e : enabled) { + var opt = profile.getOptionalFile(e); + if(opt == null) { + continue; + } + enableOptional(opt, (var1, var2) -> {}); } - enableOptional(opt, (var1, var2) -> {}); } if(this.saveJavaPath != null) { backend.getAvailableJava().thenAccept((javas) -> { From 9adf31670a0d445256843cbccd9a475151c1a52b Mon Sep 17 00:00:00 2001 From: Ap4uuk Date: Tue, 2 Jun 2026 01:27:40 +0300 Subject: [PATCH 4/5] [FIX] Preserve manual disable state for mark optionals --- .../base/profiles/optional/OptionalFile.java | 1 + .../base/profiles/optional/OptionalView.java | 3 - .../runtime/backend/ProfileSettingsImpl.java | 43 +++++----- .../config/LaunchServerConfig.java | 1 - .../launchserver/manangers/AuthManager.java | 81 +++---------------- 5 files changed, 31 insertions(+), 98 deletions(-) diff --git a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java index 6c74f129e..88d33a621 100644 --- a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java +++ b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java @@ -14,6 +14,7 @@ public class OptionalFile implements ProfileFeatureAPI.OptionalMod { public List actions; @LauncherNetworkAPI public boolean mark; + public transient boolean enabledByDefault; @LauncherNetworkAPI public boolean visible = true; @LauncherNetworkAPI diff --git a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java index 7837a1278..215468250 100644 --- a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java +++ b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java @@ -18,9 +18,6 @@ public class OptionalView { public OptionalView(ClientProfile profile) { this.all = profile.getOptional(); - for (OptionalFile f : this.all) { - if (f.mark) enable(f, true, null); - } } public OptionalView(OptionalView view) { diff --git a/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java b/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java index 1e5d57cb2..33066e370 100644 --- a/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java +++ b/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java @@ -26,8 +26,6 @@ public class ProfileSettingsImpl implements LauncherBackendAPI.ClientProfileSett @LauncherNetworkAPI private Set enabled; @LauncherNetworkAPI - private Set disabled; - @LauncherNetworkAPI private String saveJavaPath; transient OptionalView view; transient volatile JavaHelper.JavaVersion selectedJava; @@ -53,6 +51,7 @@ public ProfileSettingsImpl(ClientProfile profile, LauncherBackendImpl backend) { this.flags.add(Flag.LINUX_WAYLAND_SUPPORT); } processTriggers(profile, this.view); + applyEnabledByDefault(this.view); } @Override @@ -185,8 +184,7 @@ public ProfileSettingsImpl copy() { cloned.profile = profile; cloned.ram = new HashMap<>(ram); cloned.flags = new HashSet<>(flags); - cloned.enabled = new HashSet<>(enabled); - cloned.disabled = disabled == null ? null : new HashSet<>(disabled); + cloned.enabled = enabled == null ? null : new HashSet<>(enabled); if(view != null) { cloned.view = new OptionalView(profile, view); } @@ -197,15 +195,9 @@ public ProfileSettingsImpl copy() { public void updateEnabledMods() { enabled = new HashSet<>(); - disabled = new HashSet<>(); for(var e : view.enabled) { enabled.add(e.name); } - for (var e : view.all) { - if (e.mark && !view.isEnabled(e)) { - disabled.add(e.name); - } - } if(selectedJava != null) { saveJavaPath = selectedJava.getPath().toAbsolutePath().toString(); } @@ -216,19 +208,12 @@ public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { this.profile = profile; this.view = new OptionalView(profile); processTriggers(profile, this.view); - if (disabled != null) { - for (var e : disabled) { - var opt = profile.getOptionalFile(e); - if (opt == null) { - continue; - } - disableOptional(opt, (var1, var2) -> {}); - } - } - if (enabled != null) { + if (enabled == null) { + applyEnabledByDefault(this.view); + } else { for(var e : enabled) { var opt = profile.getOptionalFile(e); - if(opt == null) { + if (opt == null) { continue; } enableOptional(opt, (var1, var2) -> {}); @@ -257,11 +242,12 @@ public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { public void processTriggers(ClientProfile profile, OptionalView view) { TriggerManagerContext context = new TriggerManagerContext(profile); for (OptionalFile optional : view.all) { + optional.enabledByDefault = optional.mark; if (optional.limited) { if (!backend.hasPermission("launcher.runtime.optionals.%s.%s.show" .formatted(profile.getUUID(), optional.name.toLowerCase(Locale.ROOT)))) { - view.disable(optional, null); + optional.enabledByDefault = false; optional.visible = false; } else { optional.visible = true; @@ -280,10 +266,17 @@ public void processTriggers(ClientProfile profile, OptionalView view) { } } if (isRequired) { - if (fail == 0) view.enable(optional, true, null); - else view.disable(optional, null); + optional.enabledByDefault = fail == 0; } else { - if (success > 0) view.enable(optional, false, null); + if (success > 0) optional.enabledByDefault = true; + } + } + } + + private void applyEnabledByDefault(OptionalView view) { + for (OptionalFile optional : view.all) { + if (optional.visible && optional.enabledByDefault) { + view.enable(optional, false, null); } } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java b/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java index cad743dcd..3fe7b59b9 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java @@ -294,6 +294,5 @@ public static class NettySecurityConfig { public long publicKeyTokenExpire = HOURS.toSeconds(8); public long launcherTokenExpire = HOURS.toSeconds(8); - public long joinServerTimeoutMillis = 10_000; } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java index c1dca2492..44168d6a9 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java @@ -34,20 +34,13 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; public class AuthManager { public static final String WRONG_CLIENT_ERROR_MESSAGE = "Wrong Client"; - public static final String AUTH_BACKEND_TIMEOUT_ERROR_MESSAGE = "Auth backend timeout"; private transient final LaunchServer server; private transient final Logger logger = LogManager.getLogger(); private transient final JwtParser checkServerTokenParser; private transient final JwtParser clientProfileTokenParser; - private final transient Map joinServerProfilesByServerId = new ConcurrentHashMap<>(); public AuthManager(LaunchServer server) { this.server = server; @@ -214,16 +207,10 @@ public CheckServerReport checkServer(Client client, String username, String serv if(supportExtended != null) { var session = supportExtended.extendedCheckServer(client, username, serverID); if(session == null) return null; - if(!isCheckServerProfileAllowed(client, serverID)) { - throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); - } return CheckServerReport.ofUserSession(session, getPlayerProfile(client.auth, session.getUser())); } else { var user = client.auth.core.checkServer(client, username, serverID); if (user == null) return null; - if(!isCheckServerProfileAllowed(client, serverID)) { - throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); - } return CheckServerReport.ofUser(user, getPlayerProfile(client.auth, user)); } } @@ -236,71 +223,27 @@ public boolean joinServer(Client client, String username, UUID uuid, String acce username != null ? username : uuid, serverID); return false; } - long joinServerTimeoutMillis = server.config.netty.security.joinServerTimeoutMillis; - boolean result; - if (joinServerTimeoutMillis <= 0) { - result = client.auth.core.joinServer(client, username, uuid, accessToken, serverID); - } else { - var joinServerFuture = CompletableFuture.supplyAsync(() -> { - try { - return client.auth.core.joinServer(client, username, uuid, accessToken, serverID); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - try { - result = joinServerFuture.get(joinServerTimeoutMillis, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - joinServerFuture.cancel(true); - logger.warn("joinServer timed out after {} ms for user {} (serverID={})", - joinServerTimeoutMillis, username != null ? username : uuid, serverID); - throw new AuthException(AUTH_BACKEND_TIMEOUT_ERROR_MESSAGE); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("joinServer interrupted", e); - } catch (ExecutionException e) { - Throwable cause = e.getCause(); - if (cause instanceof RuntimeException runtimeException && runtimeException.getCause() instanceof IOException ioException) { - throw ioException; - } - if (cause instanceof IOException ioException) { - throw ioException; - } - throw new IOException("joinServer failed", cause); - } + if(!isJoinServerProfileAllowed(client, profileUUID, serverID)) { + throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); } - if(result && serverID != null && profileUUID != null) { - joinServerProfilesByServerId.put(serverID, profileUUID); - } - return result; + return client.auth.core.joinServer(client, username, uuid, accessToken, serverID); } - private boolean isCheckServerProfileAllowed(Client client, String serverID) { - if(serverID == null) { + private boolean isJoinServerProfileAllowed(Client client, UUID profileUUID, String serverID) { + if(client.type != AuthResponse.ConnectTypes.CLIENT || profileUUID == null || serverID == null) { return true; } - UUID expectedProfileUUID = joinServerProfilesByServerId.remove(serverID); - if(expectedProfileUUID == null) { - UUID currentProfileUUID = resolveCurrentClientProfileUUID(client); - if(currentProfileUUID == null) { + try { + UUID requestedProfileUUID = UUID.fromString(serverID); + if(profileUUID.equals(requestedProfileUUID)) { return true; } - logger.warn("checkServer denied: no joinServer profile context for serverID={}, but server profile is {}", - serverID, currentProfileUUID); - return false; - } - UUID currentProfileUUID = resolveCurrentClientProfileUUID(client); - if(currentProfileUUID == null) { - logger.warn("checkServer denied: server profile is not resolved, expected profile {} for serverID={}", - expectedProfileUUID, serverID); - return false; - } - if(!currentProfileUUID.equals(expectedProfileUUID)) { - logger.warn("checkServer denied: profile mismatch for serverID={} (expected={}, current={})", - serverID, expectedProfileUUID, currentProfileUUID); + logger.warn("joinServer denied: profile mismatch for user {} (serverID={}, selectedProfile={})", + client.username != null ? client.username : client.uuid, serverID, profileUUID); return false; + } catch (IllegalArgumentException ignored) { + return true; } - return true; } private UUID resolveCurrentClientProfileUUID(Client client) { From ab5121ac8e1ff986f4f91d2b46cb6eaf55ebbf39 Mon Sep 17 00:00:00 2001 From: Ap4uuk Date: Tue, 2 Jun 2026 02:22:10 +0300 Subject: [PATCH 5/5] Fixes and clarifications for AuthManager.java errors --- .../launchserver/manangers/AuthManager.java | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java index 44168d6a9..77dc5ce03 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java @@ -36,7 +36,6 @@ import java.util.*; public class AuthManager { - public static final String WRONG_CLIENT_ERROR_MESSAGE = "Wrong Client"; private transient final LaunchServer server; private transient final Logger logger = LogManager.getLogger(); private transient final JwtParser checkServerTokenParser; @@ -217,50 +216,9 @@ public CheckServerReport checkServer(Client client, String username, String serv public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException { if (client.auth == null) return false; - UUID profileUUID = resolveCurrentClientProfileUUID(client); - if(client.type == AuthResponse.ConnectTypes.CLIENT && profileUUID == null) { - logger.warn("joinServer denied: profile is not selected/restored for user {} (serverID={})", - username != null ? username : uuid, serverID); - return false; - } - if(!isJoinServerProfileAllowed(client, profileUUID, serverID)) { - throw new AuthException(WRONG_CLIENT_ERROR_MESSAGE); - } return client.auth.core.joinServer(client, username, uuid, accessToken, serverID); } - private boolean isJoinServerProfileAllowed(Client client, UUID profileUUID, String serverID) { - if(client.type != AuthResponse.ConnectTypes.CLIENT || profileUUID == null || serverID == null) { - return true; - } - try { - UUID requestedProfileUUID = UUID.fromString(serverID); - if(profileUUID.equals(requestedProfileUUID)) { - return true; - } - logger.warn("joinServer denied: profile mismatch for user {} (serverID={}, selectedProfile={})", - client.username != null ? client.username : client.uuid, serverID, profileUUID); - return false; - } catch (IllegalArgumentException ignored) { - return true; - } - } - - private UUID resolveCurrentClientProfileUUID(Client client) { - if(client.profile != null) { - return client.profile.getUuid(); - } - String serverName = client.getProperty("launchserver.serverName"); - if(serverName == null) { - return null; - } - try { - return UUID.fromString(serverName); - } catch (IllegalArgumentException e) { - return null; - } - } - public PlayerProfile getPlayerProfile(Client client) { if (client.auth == null) return null; PlayerProfile playerProfile;