AuthMode enum

Пакет: com.hypixel.hytale.server.core.auth

Файл: com/hypixel/hytale/server/core/auth/ServerAuthManager.java

Поля (1)

МодификаторыТипИмя
NONE, SINGLEPLAYER, EXTERNAL_SESSION, OAUTH_BROWSER, OAUTH_DEVICE, OAUTH_STORE

Исходный код

Показать/скрыть
class="kw">package com.hypixel.hytale.server.core.auth;

class="kw">import com.hypixel.hytale.codec.lookup.Priority;
class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.server.core.HytaleServer;
class="kw">import com.hypixel.hytale.server.core.Message;
class="kw">import com.hypixel.hytale.server.core.Options;
class="kw">import com.hypixel.hytale.server.core.ShutdownReason;
class="kw">import com.hypixel.hytale.server.core.auth.oauth.OAuthBrowserFlow;
class="kw">import com.hypixel.hytale.server.core.auth.oauth.OAuthClient;
class="kw">import com.hypixel.hytale.server.core.auth.oauth.OAuthDeviceFlow;
class="kw">import com.hypixel.hytale.server.core.telemetry.TelemetryService;
class="kw">import com.nimbusds.jwt.SignedJWT;
class="kw">import java.io.IOException;
class="kw">import java.security.cert.X509Certificate;
class="kw">import java.time.Duration;
class="kw">import java.time.Instant;
class="kw">import java.util.Date;
class="kw">import java.util.Map;
class="kw">import java.util.UUID;
class="kw">import java.util.concurrent.CompletableFuture;
class="kw">import java.util.concurrent.CompletionException;
class="kw">import java.util.concurrent.ConcurrentHashMap;
class="kw">import java.util.concurrent.Executors;
class="kw">import java.util.concurrent.ScheduledExecutorService;
class="kw">import java.util.concurrent.ScheduledFuture;
class="kw">import java.util.concurrent.TimeUnit;
class="kw">import java.util.concurrent.atomic.AtomicReference;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;
class="kw">import joptsimple.OptionSet;

class="kw">public class ServerAuthManager {
   class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
   class="kw">private class="kw">static class="kw">final int REFRESH_BUFFER_SECONDS = 300;
   class="kw">private class="kw">static class="kw">final int REFRESH_MAX_RETRIES = 3;
   class="kw">private class="kw">static class="kw">final int REFRESH_RETRY_BASE_DELAY_SECONDS = 30;
   class="kw">private class="kw">static class="kw">volatile ServerAuthManager instance;
   class="kw">private class="kw">volatile ServerAuthManager.AuthMode authMode = ServerAuthManager.AuthMode.NONE;
   class="kw">private class="kw">volatile Instant tokenExpiry;
   class="kw">private class="kw">final AtomicReference<SessionServiceClient.GameSessionResponse> gameSession = new AtomicReference<>();
   class="kw">private class="kw">final AtomicReference<IAuthCredentialStore> credentialStore = new AtomicReference<>(new DefaultAuthCredentialStore());
   class="kw">private class="kw">final Map<UUID, SessionServiceClient.GameProfile> availableProfiles = new ConcurrentHashMap<>();
   class="kw">private class="kw">volatile SessionServiceClient.GameProfile[] pendingProfiles;
   class="kw">private class="kw">volatile ServerAuthManager.AuthMode pendingAuthMode;
   class="kw">private class="kw">final AtomicReference<X509Certificate> serverCertificate = new AtomicReference<>();
   class="kw">private class="kw">final UUID serverSessionId = UUID.randomUUID();
   class="kw">private class="kw">volatile boolean isSingleplayer;
   class="kw">private OAuthClient oauthClient;
   class="kw">private class="kw">volatile SessionServiceClient sessionServiceClient;
   class="kw">private class="kw">volatile ProfileServiceClient profileServiceClient;
   class="kw">private class="kw">final ScheduledExecutorService refreshScheduler = Executors.newSingleThreadScheduledExecutor(var0 -> {
      Thread var1 = new Thread(var0, "TokenRefresh");
      var1.setDaemon(true);
      class="kw">return var1;
   });
   class="kw">private ScheduledFuture<?> refreshTask;
   class="kw">private Runnable cancelActiveFlow;
   class="kw">private class="kw">volatile Message pendingFatalError;

   class="kw">private ServerAuthManager() {
   }

   class="kw">public class="kw">static ServerAuthManager getInstance() {
      if (instance == null) {
         class="kw">synchronized (ServerAuthManager.class) {
            if (instance == null) {
               instance = new ServerAuthManager();
            }
         }
      }

      class="kw">return instance;
   }

   @Nonnull
   class="kw">public ProfileServiceClient getProfileServiceClient() {
      if (this.profileServiceClient == null) {
         class="kw">synchronized (this) {
            if (this.profileServiceClient == null) {
               this.profileServiceClient = new ProfileServiceClient("https://account-data.hytale.com");
            }
         }
      }

      class="kw">return this.profileServiceClient;
   }

   class="kw">public void initialize() {
      OptionSet var1 = Options.getOptionSet();
      if (var1 == null) {
         LOGGER.at(Level.WARNING).log("Options not parsed, cannot initialize ServerAuthManager");
      } else {
         this.oauthClient = new OAuthClient();
         this.isSingleplayer = var1.has(Options.SINGLEPLAYER);
         if (this.isSingleplayer && var1.has(Options.OWNER_UUID)) {
            SessionServiceClient.GameProfile var2 = new SessionServiceClient.GameProfile();
            var2.uuid = (UUID)var1.valueOf(Options.OWNER_UUID);
            var2.username = var1.has(Options.OWNER_NAME) ? (String)var1.valueOf(Options.OWNER_NAME) : null;
            this.credentialStore.get().setProfile(var2.uuid);
            LOGGER.at(Level.INFO).log("Singleplayer mode, owner: %s (%s)", var2.username, var2.uuid);
         }

         if (this.isSingleplayer && var1.valueOf(Options.AUTH_MODE) == Options.AuthMode.OFFLINE) {
            String var7 = System.getenv("HYTALE_SERVER_OFFLINE_TOKEN");
            if (var7 != null && !var7.isEmpty()) {
               LOGGER.at(Level.INFO).log("Offline token loaded from environment");
               if (this.validateOfflineToken(var7)) {
                  LOGGER.at(Level.INFO).log("Offline token validated, singleplayer offline mode");
                  LOGGER.at(Level.INFO).log("Server session ID: %s", this.serverSessionId);
               } else {
                  this.pendingFatalError = Message.translation("client.disconnection.shutdownReason.authFailed.offlineTokenValidationFailed");
                  LOGGER.at(Level.SEVERE).log("%s", this.pendingFatalError.getAnsiMessage());
               }
            } else {
               this.pendingFatalError = Message.translation("client.disconnection.shutdownReason.authFailed.needOfficialLauncher");
               LOGGER.at(Level.SEVERE).log("%s", this.pendingFatalError.getAnsiMessage());
            }
         } else {
            boolean var6 = false;
            String var3 = null;
            String var4 = null;
            if (var1.has(Options.SESSION_TOKEN)) {
               var3 = (String)var1.valueOf(Options.SESSION_TOKEN);
               LOGGER.at(Level.INFO).log("Session token loaded from CLI");
            } else {
               String var5 = System.getenv("HYTALE_SERVER_SESSION_TOKEN");
               if (var5 != null && !var5.isEmpty()) {
                  var3 = var5;
                  LOGGER.at(Level.INFO).log("Session token loaded from environment");
               }
            }

            if (var1.has(Options.IDENTITY_TOKEN)) {
               var4 = (String)var1.valueOf(Options.IDENTITY_TOKEN);
               LOGGER.at(Level.INFO).log("Identity token loaded from CLI");
            } else {
               String var8 = System.getenv("HYTALE_SERVER_IDENTITY_TOKEN");
               if (var8 != null && !var8.isEmpty()) {
                  var4 = var8;
                  LOGGER.at(Level.INFO).log("Identity token loaded from environment");
               }
            }

            if (var3 != null || var4 != null) {
               if (this.validateInitialTokens(var3, var4)) {
                  SessionServiceClient.GameSessionResponse var9 = new SessionServiceClient.GameSessionResponse();
                  var9.sessionToken = var3;
                  var9.identityToken = var4;
                  this.gameSession.set(var9);
                  var6 = true;
               } else {
                  this.pendingFatalError = Message.translation("client.disconnection.shutdownReason.authFailed.tokenValidationFailed");
                  LOGGER.at(Level.SEVERE).log("%s", this.pendingFatalError.getAnsiMessage());
               }
            }

            if (var6) {
               if (this.isSingleplayer) {
                  this.authMode = ServerAuthManager.AuthMode.SINGLEPLAYER;
                  LOGGER.at(Level.INFO).log("Auth mode: SINGLEPLAYER");
               } else {
                  this.authMode = ServerAuthManager.AuthMode.EXTERNAL_SESSION;
                  LOGGER.at(Level.INFO).log("Auth mode: EXTERNAL_SESSION");
               }

               this.parseAndScheduleRefresh();
            } else {
               LOGGER.at(Level.INFO).log("No server tokens configured. Use /auth login to authenticate, or provide tokens via CLI/environment.");
            }

            LOGGER.at(Level.INFO).log("Server session ID: %s", this.serverSessionId);
            LOGGER.at(Level.FINE)
               .log(
                  "ServerAuthManager initialized - session token: %s, identity token: %s, auth mode: %s",
                  this.hasSessionToken() ? "present" : "missing",
                  this.hasIdentityToken() ? "present" : "missing",
                  this.authMode
               );
         }
      }
   }

   class="kw">public void checkPendingFatalError() {
      if (this.pendingFatalError != null) {
         HytaleServer.get().shutdownServer(ShutdownReason.AUTH_FAILED.withMessage(this.pendingFatalError));
      }
   }

   class="kw">public void initializeCredentialStore() {
      AuthCredentialStoreProvider var1 = HytaleServer.get().getConfig().getAuthCredentialStoreProvider();
      this.credentialStore.set(var1.createStore());
      LOGGER.at(Level.INFO)
         .log("Auth credential store: %s", AuthCredentialStoreProvider.CODEC.getIdFor((Class<? class="kw">extends AuthCredentialStoreProvider>)var1.getClass()));
      IAuthCredentialStore var2 = this.credentialStore.get();
      IAuthCredentialStore.OAuthTokens var3 = var2.getTokens();
      if (var3.isValid()) {
         LOGGER.at(Level.INFO).log("Found stored credentials, attempting to restore session...");
         ServerAuthManager.AuthResult var4 = this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_STORE);
         class="kw">switch (var4) {
            case SUCCESS:
               LOGGER.at(Level.INFO).log("Session restored from stored credentials");
               break;
            case PENDING_PROFILE_SELECTION:
               LOGGER.at(Level.INFO).log("Session restored but profile selection required - use /auth select");
               break;
            class="kw">default:
               LOGGER.at(Level.WARNING).log("Failed to restore session from stored credentials");
         }
      }
   }

   class="kw">public void shutdown() {
      this.cancelActiveFlow();
      this.refreshScheduler.shutdownNow();
      if (this.isSingleplayer()) {
         String var1 = this.getSessionToken();
         if (var1 != null && !var1.isEmpty()) {
            if (this.sessionServiceClient == null) {
               this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
            }

            this.sessionServiceClient.terminateSession(var1);
         }
      }
   }

   class="kw">public void logout() {
      this.cancelActiveFlow();
      if (this.refreshTask != null) {
         this.refreshTask.cancel(false);
         this.refreshTask = null;
      }

      this.gameSession.set(null);
      this.credentialStore.get().clear();
      this.availableProfiles.clear();
      this.pendingProfiles = null;
      this.pendingAuthMode = null;
      this.tokenExpiry = null;
      this.authMode = ServerAuthManager.AuthMode.NONE;
      LOGGER.at(Level.INFO).log("Server logged out");
   }

   @Nullable
   class="kw">public SessionServiceClient.GameSessionResponse getGameSession() {
      class="kw">return this.gameSession.get();
   }

   class="kw">public void setGameSession(@Nonnull SessionServiceClient.GameSessionResponse var1) {
      this.gameSession.set(var1);
      LOGGER.at(Level.FINE).log("Game session updated");
   }

   @Nullable
   class="kw">public String getIdentityToken() {
      SessionServiceClient.GameSessionResponse var1 = this.gameSession.get();
      class="kw">return var1 != null ? var1.identityToken : null;
   }

   @Nullable
   class="kw">public String getSessionToken() {
      SessionServiceClient.GameSessionResponse var1 = this.gameSession.get();
      class="kw">return var1 != null ? var1.sessionToken : null;
   }

   class="kw">public boolean hasIdentityToken() {
      SessionServiceClient.GameSessionResponse var1 = this.gameSession.get();
      class="kw">return var1 != null && var1.identityToken != null;
   }

   class="kw">public boolean hasSessionToken() {
      SessionServiceClient.GameSessionResponse var1 = this.gameSession.get();
      class="kw">return var1 != null && var1.sessionToken != null;
   }

   @Nullable
   class="kw">public String getOAuthAccessToken() {
      if (!this.refreshOAuthTokens()) {
         class="kw">return null;
      }

      IAuthCredentialStore var1 = this.credentialStore.get();
      class="kw">return var1.getTokens().accessToken();
   }

   class="kw">public void setServerCertificate(@Nonnull X509Certificate var1) {
      this.serverCertificate.set(var1);
      LOGGER.at(Level.INFO).log("Server certificate set: %s", var1.getSubjectX500Principal());
   }

   @Nullable
   class="kw">public X509Certificate getServerCertificate() {
      class="kw">return this.serverCertificate.get();
   }

   @Nullable
   class="kw">public String getServerCertificateFingerprint() {
      X509Certificate var1 = this.serverCertificate.get();
      class="kw">return var1 == null ? null : CertificateUtil.computeCertificateFingerprint(var1);
   }

   @Nonnull
   class="kw">public UUID getServerSessionId() {
      class="kw">return this.serverSessionId;
   }

   class="kw">public ServerAuthManager.AuthMode getAuthMode() {
      class="kw">return this.authMode;
   }

   class="kw">public boolean isSingleplayer() {
      class="kw">return this.isSingleplayer;
   }

   class="kw">public boolean isOwner(@Nullable UUID var1) {
      UUID var2 = this.credentialStore.get().getProfile();
      class="kw">return var2 != null && var2.equals(var1);
   }

   @Nullable
   class="kw">public SessionServiceClient.GameProfile getSelectedProfile() {
      UUID var1 = this.credentialStore.get().getProfile();
      class="kw">return var1 == null ? null : this.availableProfiles.get(var1);
   }

   @Nullable
   class="kw">public Instant getTokenExpiry() {
      class="kw">return this.tokenExpiry;
   }

   class="kw">public String getAuthStatus() {
      StringBuilder var1 = new StringBuilder();
      var1.append(this.authMode.name());
      if (this.hasSessionToken() && this.hasIdentityToken()) {
         var1.append(" (authenticated)");
      } else if (!this.hasSessionToken() && !this.hasIdentityToken()) {
         var1.append(" (no tokens)");
      } else {
         var1.append(" (partial)");
      }

      if (this.tokenExpiry != null) {
         long var2 = this.tokenExpiry.getEpochSecond() - Instant.now().getEpochSecond();
         if (var2 > 0L) {
            var1.append(String.format(" [expires in %dm %ds]", var2 / 60L, var2 % 60L));
         } else {
            var1.append(" [EXPIRED]");
         }
      }

      class="kw">return var1.toString();
   }

   class="kw">public CompletableFuture<ServerAuthManager.AuthResult> startFlowAsync(@Nonnull OAuthBrowserFlow var1) {
      if (this.isSingleplayer) {
         class="kw">return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED);
      }

      this.cancelActiveFlow();
      this.cancelActiveFlow = this.oauthClient.startFlow(var1);
      class="kw">return var1.getFuture().thenApply(var2 -> {
         class="kw">switch (var2) {
            case SUCCESS:
               IAuthCredentialStore var3 = this.credentialStore.get();
               OAuthClient.TokenResponse var4 = var1.getTokenResponse();
               var3.setTokens(new IAuthCredentialStore.OAuthTokens(var4.accessToken(), var4.refreshToken(), Instant.now().plusSeconds(var4.expiresIn())));
               class="kw">return this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_BROWSER);
            case FAILED:
               LOGGER.at(Level.WARNING).log("OAuth browser flow failed: %s", var1.getErrorMessage());
               class="kw">return ServerAuthManager.AuthResult.FAILED;
            case UNKNOWN:
            class="kw">default:
               LOGGER.at(Level.WARNING).log("OAuth browser flow completed with unexpected result: %s", var2);
               class="kw">return ServerAuthManager.AuthResult.FAILED;
         }
      });
   }

   class="kw">public CompletableFuture<ServerAuthManager.AuthResult> startFlowAsync(OAuthDeviceFlow var1) {
      if (this.isSingleplayer) {
         class="kw">return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED);
      }

      this.cancelActiveFlow();
      this.cancelActiveFlow = this.oauthClient.startFlow(var1);
      class="kw">return var1.getFuture().thenApply(var2 -> {
         class="kw">switch (var2) {
            case SUCCESS:
               IAuthCredentialStore var3 = this.credentialStore.get();
               OAuthClient.TokenResponse var4 = var1.getTokenResponse();
               var3.setTokens(new IAuthCredentialStore.OAuthTokens(var4.accessToken(), var4.refreshToken(), Instant.now().plusSeconds(var4.expiresIn())));
               class="kw">return this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_DEVICE);
            case FAILED:
               LOGGER.at(Level.WARNING).log("OAuth device flow failed: %s", var1.getErrorMessage());
               class="kw">return ServerAuthManager.AuthResult.FAILED;
            case UNKNOWN:
            class="kw">default:
               LOGGER.at(Level.WARNING).log("OAuth device flow completed with unexpected result: %s", var2);
               class="kw">return ServerAuthManager.AuthResult.FAILED;
         }
      });
   }

   class="kw">public CompletableFuture<ServerAuthManager.AuthResult> registerCredentialStore(IAuthCredentialStore var1) {
      if (this.isSingleplayer) {
         class="kw">return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED);
      }

      if (this.hasSessionToken() && this.hasIdentityToken()) {
         class="kw">return CompletableFuture.completedFuture(ServerAuthManager.AuthResult.FAILED);
      }

      this.credentialStore.set(var1);
      class="kw">return CompletableFuture.completedFuture(this.createGameSessionFromOAuth(ServerAuthManager.AuthMode.OAUTH_STORE));
   }

   class="kw">public void swapCredentialStoreProvider(@Nonnull AuthCredentialStoreProvider var1) {
      IAuthCredentialStore var2 = this.credentialStore.get();
      IAuthCredentialStore var3 = var1.createStore();
      IAuthCredentialStore.OAuthTokens var4 = var2.getTokens();
      if (var4.isValid()) {
         var3.setTokens(var4);
      }

      UUID var5 = var2.getProfile();
      if (var5 != null) {
         var3.setProfile(var5);
      }

      this.credentialStore.set(var3);
      LOGGER.at(Level.INFO).log("Swapped credential store to: %s", var1.getClass().getSimpleName());
   }

   class="kw">public boolean cancelActiveFlow() {
      if (this.cancelActiveFlow != null) {
         this.cancelActiveFlow.run();
         this.cancelActiveFlow = null;
         class="kw">return true;
      } else {
         class="kw">return false;
      }
   }

   @Nullable
   class="kw">public SessionServiceClient.GameProfile[] getPendingProfiles() {
      class="kw">return this.pendingProfiles;
   }

   class="kw">public boolean hasPendingProfiles() {
      class="kw">return this.pendingProfiles != null && this.pendingProfiles.length > 0;
   }

   class="kw">public boolean selectPendingProfile(int var1) {
      SessionServiceClient.GameProfile[] var2 = this.pendingProfiles;
      ServerAuthManager.AuthMode var3 = this.pendingAuthMode;
      if (var2 == null || var2.length == 0) {
         LOGGER.at(Level.WARNING).log("No pending profiles to select");
         class="kw">return false;
      } else if (var1 >= 1 && var1 <= var2.length) {
         SessionServiceClient.GameProfile var4 = var2[var1 - 1];
         LOGGER.at(Level.INFO).log("Selected profile: %s (%s)", var4.username, var4.uuid);
         class="kw">return this.completeAuthWithProfile(var4, var3 != null ? var3 : ServerAuthManager.AuthMode.OAUTH_BROWSER);
      } else {
         LOGGER.at(Level.WARNING).log("Invalid profile index: %d (valid range: 1-%d)", var1, var2.length);
         class="kw">return false;
      }
   }

   class="kw">public boolean selectPendingProfileByUsername(String var1) {
      SessionServiceClient.GameProfile[] var2 = this.pendingProfiles;
      ServerAuthManager.AuthMode var3 = this.pendingAuthMode;
      if (var2 != null && var2.length != 0) {
         for (SessionServiceClient.GameProfile var7 : var2) {
            if (var7.username != null && var7.username.equalsIgnoreCase(var1)) {
               LOGGER.at(Level.INFO).log("Selected profile: %s (%s)", var7.username, var7.uuid);
               class="kw">return this.completeAuthWithProfile(var7, var3 != null ? var3 : ServerAuthManager.AuthMode.OAUTH_BROWSER);
            }
         }

         LOGGER.at(Level.WARNING).log("No profile found with username: %s", var1);
         class="kw">return false;
      } else {
         LOGGER.at(Level.WARNING).log("No pending profiles to select");
         class="kw">return false;
      }
   }

   class="kw">public void clearPendingProfiles() {
      this.pendingProfiles = null;
      this.pendingAuthMode = null;
   }

   class="kw">private boolean validateOfflineToken(@Nonnull String var1) {
      if (this.sessionServiceClient == null) {
         this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
      }

      JWTValidator var2 = new JWTValidator(this.sessionServiceClient, "https://sessions.hytale.com", "");
      JWTValidator.IdentityTokenClaims var3 = var2.validateOfflineToken(var1);
      if (var3 == null) {
         LOGGER.at(Level.WARNING).log("Offline token validation failed");
         class="kw">return false;
      }

      OptionSet var4 = Options.getOptionSet();
      UUID var5 = var4 != null && var4.has(Options.OWNER_UUID) ? (UUID)var4.valueOf(Options.OWNER_UUID) : null;
      String var6 = var4 != null && var4.has(Options.OWNER_NAME) ? (String)var4.valueOf(Options.OWNER_NAME) : null;
      UUID var7 = var3.getSubjectAsUUID();
      if (var5 == null || var7 != null && var7.equals(var5)) {
         if (var6 != null && var3.username != null && !var3.username.equals(var6)) {
            LOGGER.at(Level.WARNING).log("Offline token username mismatch: token has '%s', expected '%s'", var3.username, var6);
            class="kw">return false;
         } else {
            LOGGER.at(Level.INFO).log("Offline token validated for %s (%s)", var3.username, var3.subject);
            class="kw">return true;
         }
      } else {
         LOGGER.at(Level.WARNING).log("Offline token UUID mismatch: token has %s, expected %s", var3.subject, var5);
         class="kw">return false;
      }
   }

   class="kw">private boolean validateInitialTokens(@Nullable String var1, @Nullable String var2) {
      if (var1 == null && var2 == null) {
         class="kw">return false;
      }

      if (this.sessionServiceClient == null) {
         this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
      }

      JWTValidator var3 = new JWTValidator(this.sessionServiceClient, "https://sessions.hytale.com", "");
      boolean var4 = true;
      OptionSet var5 = Options.getOptionSet();
      UUID var6 = var5 != null && var5.has(Options.OWNER_UUID) ? (UUID)var5.valueOf(Options.OWNER_UUID) : null;
      String var7 = var5 != null && var5.has(Options.OWNER_NAME) ? (String)var5.valueOf(Options.OWNER_NAME) : null;
      if (var2 != null) {
         JWTValidator.IdentityTokenClaims var8 = var3.validateIdentityToken(var2);
         if (var8 == null) {
            LOGGER.at(Level.WARNING).log("Identity token validation failed");
            var4 = false;
         } else if (!var8.hasScope("hytale:server")) {
            LOGGER.at(Level.WARNING).log("Identity token missing required scope: expected %s, got %s", "hytale:server", var8.scope);
            var4 = false;
         } else {
            if (var6 != null) {
               UUID var9 = var8.getSubjectAsUUID();
               if (var9 == null || !var9.equals(var6)) {
                  LOGGER.at(Level.WARNING).log("Identity token UUID mismatch: token has %s, expected %s", var8.subject, var6);
                  var4 = false;
               }
            }

            if (var7 != null && var8.username != null && !var8.username.equals(var7)) {
               LOGGER.at(Level.WARNING).log("Identity token username mismatch: token has '%s', expected '%s'", var8.username, var7);
               var4 = false;
            }

            if (var4) {
               LOGGER.at(Level.INFO).log("Identity token validated for %s (%s)", var8.username, var8.subject);
            }
         }
      }

      if (var1 != null) {
         JWTValidator.SessionTokenClaims var10 = var3.validateSessionToken(var1);
         if (var10 == null) {
            LOGGER.at(Level.WARNING).log("Session token validation failed");
            var4 = false;
         } else {
            if (var6 != null) {
               UUID var11 = var10.getSubjectAsUUID();
               if (var11 == null || !var11.equals(var6)) {
                  LOGGER.at(Level.WARNING).log("Session token UUID mismatch: token has %s, expected %s", var10.subject, var6);
                  var4 = false;
               }
            }

            if (var4) {
               LOGGER.at(Level.INFO).log("Session token validated");
            }
         }
      }

      class="kw">return var4;
   }

   class="kw">private ServerAuthManager.AuthResult createGameSessionFromOAuth(ServerAuthManager.AuthMode var1) {
      if (!this.refreshOAuthTokens()) {
         LOGGER.at(Level.WARNING).log("No valid OAuth tokens to create game session");
         class="kw">return ServerAuthManager.AuthResult.FAILED;
      }

      IAuthCredentialStore var2 = this.credentialStore.get();
      String var3 = var2.getTokens().accessToken();
      if (var3 == null) {
         LOGGER.at(Level.WARNING).log("No access token in credential store");
         class="kw">return ServerAuthManager.AuthResult.FAILED;
      }

      if (this.sessionServiceClient == null) {
         this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
      }

      SessionServiceClient.GameProfile[] var4 = this.sessionServiceClient.getGameProfiles(var3);
      if (var4 != null && var4.length != 0) {
         this.availableProfiles.clear();

         for (SessionServiceClient.GameProfile var8 : var4) {
            this.availableProfiles.put(var8.uuid, var8);
         }

         SessionServiceClient.GameProfile var9 = this.tryAutoSelectProfile(var4);
         if (var9 != null) {
            class="kw">return this.completeAuthWithProfile(var9, var1) ? ServerAuthManager.AuthResult.SUCCESS : ServerAuthManager.AuthResult.FAILED;
         }

         this.pendingProfiles = var4;
         this.pendingAuthMode = var1;
         this.cancelActiveFlow = null;
         LOGGER.at(Level.INFO).log("Multiple profiles available. Use '/auth select <number>' to choose:");

         for (int var10 = 0; var10 < var4.length; var10++) {
            LOGGER.at(Level.INFO).log("  [%d] %s (%s)", var10 + 1, var4[var10].username, var4[var10].uuid);
         }

         class="kw">return ServerAuthManager.AuthResult.PENDING_PROFILE_SELECTION;
      } else {
         LOGGER.at(Level.WARNING).log("No game profiles found for this account");
         class="kw">return ServerAuthManager.AuthResult.FAILED;
      }
   }

   class="kw">private boolean refreshOAuthTokens() {
      class="kw">return this.refreshOAuthTokens(false);
   }

   class="kw">private boolean refreshOAuthTokens(boolean var1) {
      IAuthCredentialStore var2 = this.credentialStore.get();
      IAuthCredentialStore.OAuthTokens var3 = var2.getTokens();
      Instant var4 = var3.accessTokenExpiresAt();
      if (!var1 && var4 != null && !var4.isBefore(Instant.now().plusSeconds(300L))) {
         class="kw">return true;
      }

      String var5 = var3.refreshToken();
      if (var5 == null) {
         LOGGER.at(Level.WARNING).log("No refresh token present to refresh OAuth tokens");
         class="kw">return false;
      }

      for (int var6 = 1; var6 <= 3; var6++) {
         if (var6 > 1) {
            LOGGER.at(Level.INFO).log("Refreshing OAuth tokens (attempt %d/%d)...", var6, 3);
         } else {
            LOGGER.at(Level.INFO).log("Refreshing OAuth tokens...");
         }

         try {
            OAuthClient.TokenResponse var7 = this.oauthClient.refreshTokens(var5);
            if (var7 != null && var7.isSuccess()) {
               var2.setTokens(new IAuthCredentialStore.OAuthTokens(var7.accessToken(), var7.refreshToken(), Instant.now().plusSeconds(var7.expiresIn())));
               class="kw">return true;
            }

            LOGGER.at(Level.WARNING).log("OAuth token refresh rejected by server");
            class="kw">return false;
         } catch (InterruptedException var12) {
            Thread.currentThread().interrupt();
            LOGGER.at(Level.WARNING).log("OAuth token refresh interrupted");
            class="kw">return false;
         } catch (IOException var13) {
            if (var6 < 3) {
               long var8 = 30L * (1L << var6 - 1);
               LOGGER.at(Level.WARNING).log("OAuth token refresh IO error (attempt %d/%d), retrying in %d seconds...", var6, 3, var8);

               try {
                  Thread.sleep(Duration.ofSeconds(var8));
               } catch (InterruptedException var11) {
                  Thread.currentThread().interrupt();
                  LOGGER.at(Level.WARNING).log("OAuth token refresh retry interrupted");
                  class="kw">return false;
               }
            } else {
               LOGGER.at(Level.WARNING).log("OAuth token refresh failed after %d attempts due to IO errors", 3);
            }
         }
      }

      class="kw">return false;
   }

   @Nullable
   class="kw">private SessionServiceClient.GameProfile tryAutoSelectProfile(SessionServiceClient.GameProfile[] var1) {
      OptionSet var2 = Options.getOptionSet();
      if (var2 != null && var2.has(Options.OWNER_UUID)) {
         UUID var8 = (UUID)var2.valueOf(Options.OWNER_UUID);

         for (SessionServiceClient.GameProfile var12 : var1) {
            if (var12.uuid.equals(var8)) {
               LOGGER.at(Level.INFO).log("Selected profile from --owner-uuid: %s (%s)", var12.username, var12.uuid);
               class="kw">return var12;
            }
         }

         LOGGER.at(Level.WARNING).log("Specified --owner-uuid %s not found in available profiles", var8);
         class="kw">return null;
      } else {
         if (var1.length == 1) {
            LOGGER.at(Level.INFO).log("Auto-selected profile: %s (%s)", var1[0].username, var1[0].uuid);
            class="kw">return var1[0];
         }

         UUID var3 = this.credentialStore.get().getProfile();
         if (var3 != null) {
            for (SessionServiceClient.GameProfile var7 : var1) {
               if (var7.uuid.equals(var3)) {
                  LOGGER.at(Level.INFO).log("Auto-selected profile from storage: %s (%s)", var7.username, var7.uuid);
                  class="kw">return var7;
               }
            }
         }

         class="kw">return null;
      }
   }

   class="kw">private boolean completeAuthWithProfile(SessionServiceClient.GameProfile var1, ServerAuthManager.AuthMode var2) {
      SessionServiceClient.GameSessionResponse var3 = this.createGameSession(var1.uuid);
      if (var3 == null) {
         LOGGER.at(Level.WARNING).log("Failed to create game session");
         class="kw">return false;
      }

      this.gameSession.set(var3);
      this.authMode = var2;
      this.cancelActiveFlow = null;
      this.pendingProfiles = null;
      this.pendingAuthMode = null;
      Instant var4 = this.getEffectiveExpiry(var3);
      if (var4 != null) {
         this.setExpiryAndScheduleRefresh(var4);
      }

      LOGGER.at(Level.INFO).log("Authentication successful! Mode: %s", var2);
      TelemetryService var5 = TelemetryService.get();
      if (var5 != null) {
         var5.onAuthenticated();
      }

      class="kw">return true;
   }

   @Nullable
   class="kw">private SessionServiceClient.GameSessionResponse createGameSession(UUID var1) {
      if (this.sessionServiceClient == null) {
         this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
      }

      if (!this.refreshOAuthTokens()) {
         LOGGER.at(Level.WARNING).log("OAuth token refresh for game session creation failed");
         class="kw">return null;
      }

      IAuthCredentialStore var2 = this.credentialStore.get();
      String var3 = var2.getTokens().accessToken();
      SessionServiceClient.GameSessionResponse var4 = this.sessionServiceClient.createGameSession(var3, var1);
      if (var4 == null) {
         LOGGER.at(Level.WARNING).log("Trying force refresh of OAuth tokens because game session creation failed");
         if (!this.refreshOAuthTokens(true)) {
            LOGGER.at(Level.WARNING).log("Force refresh failed");
            class="kw">return null;
         }

         var4 = this.sessionServiceClient.createGameSession(var3, var1);
         if (var4 == null) {
            LOGGER.at(Level.WARNING).log("Game session creation with force refreshed tokens failed");
            class="kw">return null;
         }
      }

      var2.setProfile(var1);
      class="kw">return var4;
   }

   class="kw">private void parseAndScheduleRefresh() {
      SessionServiceClient.GameSessionResponse var1 = this.gameSession.get();
      Instant var2 = this.getEffectiveExpiry(var1);
      if (var2 != null) {
         this.setExpiryAndScheduleRefresh(var2);
      }
   }

   @Nullable
   class="kw">private Instant getEffectiveExpiry(@Nullable SessionServiceClient.GameSessionResponse var1) {
      Instant var2 = var1 != null ? var1.getExpiresAtInstant() : null;
      Instant var3 = this.parseIdentityTokenExpiry(var1 != null ? var1.identityToken : this.getIdentityToken());
      if (var2 != null && var3 != null) {
         class="kw">return var2.isBefore(var3) ? var2 : var3;
      } else {
         class="kw">return var2 != null ? var2 : var3;
      }
   }

   @Nullable
   class="kw">private Instant parseIdentityTokenExpiry(@Nullable String var1) {
      if (var1 == null) {
         class="kw">return null;
      }

      try {
         Date var2 = SignedJWT.parse(var1).getJWTClaimsSet().getExpirationTime();
         if (var2 != null) {
            class="kw">return var2.toInstant();
         }
      } catch (Exception var3) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var3)).log("Failed to parse identity token expiry");
      }

      class="kw">return null;
   }

   class="kw">private void setExpiryAndScheduleRefresh(@Nonnull Instant var1) {
      this.tokenExpiry = var1;
      if (this.refreshTask != null) {
         this.refreshTask.cancel(false);
      }

      long var2 = var1.getEpochSecond() - Instant.now().getEpochSecond();
      if (var2 > 300L) {
         long var4 = Math.max(var2 - 300L, 60L);
         LOGGER.at(Level.INFO).log("Token refresh scheduled in %d seconds", var4);
         this.refreshTask = this.refreshScheduler.schedule(() -> this.attemptSessionRefresh(1), var4, TimeUnit.SECONDS);
      }
   }

   class="kw">private void attemptSessionRefresh(int var1) {
      String var2 = this.getSessionToken();
      if (var2 != null) {
         if (var1 > 1) {
            LOGGER.at(Level.INFO).log("Refreshing game session with Session Service (attempt %d/%d)...", var1, 3);
         } else {
            LOGGER.at(Level.INFO).log("Refreshing game session with Session Service...");
         }

         try {
            if (this.refreshGameSession(var2)) {
               class="kw">return;
            }
         } catch (CompletionException var6) {
            if (var6.getCause() class="kw">instanceof IOException && var1 < 3) {
               long var4 = 30L * (1L << var1 - 1);
               LOGGER.at(Level.WARNING).log("Game session refresh IO error (attempt %d/%d), retrying in %d seconds...", var1, 3, var4);
               this.refreshTask = this.refreshScheduler.schedule(() -> this.attemptSessionRefresh(var1 + 1), var4, TimeUnit.SECONDS);
               class="kw">return;
            }

            if (var6.getCause() class="kw">instanceof IOException) {
               LOGGER.at(Level.WARNING).log("Game session refresh failed after %d attempts due to IO errors", 3);
            } else {
               LOGGER.at(Level.WARNING).log("Session Service refresh failed: %s", var6.getMessage());
            }
         } catch (Exception var7) {
            LOGGER.at(Level.WARNING).log("Session Service refresh failed: %s", var7.getMessage());
         }
      }

      LOGGER.at(Level.INFO).log("Game session refresh failed, attempting OAuth refresh...");
      if (!this.refreshGameSessionViaOAuth()) {
         LOGGER.at(Level.WARNING).log("All refresh attempts failed. Server may lose authentication.");
      }
   }

   class="kw">private boolean refreshGameSession(String var1) {
      if (this.sessionServiceClient == null) {
         this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
      }

      SessionServiceClient.GameSessionResponse var2 = this.sessionServiceClient.refreshSessionAsync(var1).join();
      if (var2 == null) {
         LOGGER.at(Level.WARNING).log("Game session refresh rejected by server");
         class="kw">return false;
      }

      this.gameSession.set(var2);
      Instant var3 = this.getEffectiveExpiry(var2);
      if (var3 != null) {
         this.setExpiryAndScheduleRefresh(var3);
      }

      LOGGER.at(Level.INFO).log("Game session refresh successful");
      class="kw">return true;
   }

   class="kw">private boolean refreshGameSessionViaOAuth() {
      boolean var1 = class="kw">switch (this.authMode) {
         case OAUTH_BROWSER, OAUTH_DEVICE, OAUTH_STORE -> true;
         class="kw">default -> false;
      };
      if (!var1) {
         LOGGER.at(Level.WARNING).log("Refresh via OAuth not supported for current Auth Mode");
         class="kw">return false;
      }

      UUID var2 = this.credentialStore.get().getProfile();
      if (var2 == null) {
         LOGGER.at(Level.WARNING).log("No current profile, cannot refresh game session");
         class="kw">return false;
      }

      SessionServiceClient.GameSessionResponse var3 = this.createGameSession(var2);
      if (var3 == null) {
         LOGGER.at(Level.WARNING).log("Failed to create new game session");
         class="kw">return false;
      }

      this.gameSession.set(var3);
      Instant var4 = this.getEffectiveExpiry(var3);
      if (var4 != null) {
         this.setExpiryAndScheduleRefresh(var4);
      }

      LOGGER.at(Level.INFO).log("New game session created via OAuth refresh");
      class="kw">return true;
   }

   class="kw">static {
      AuthCredentialStoreProvider.CODEC.register("Memory", MemoryAuthCredentialStoreProvider.class, MemoryAuthCredentialStoreProvider.CODEC);
      AuthCredentialStoreProvider.CODEC
         .register(Priority.DEFAULT, "Encrypted", EncryptedAuthCredentialStoreProvider.class, EncryptedAuthCredentialStoreProvider.CODEC);
   }

   class="kw">public enum AuthMode {
      NONE,
      SINGLEPLAYER,
      EXTERNAL_SESSION,
      OAUTH_BROWSER,
      OAUTH_DEVICE,
      OAUTH_STORE;

      AuthMode() {
      }
   }

   class="kw">public enum AuthResult {
      SUCCESS,
      PENDING_PROFILE_SELECTION,
      FAILED;

      AuthResult() {
      }
   }
}