AuthState enum

Пакет: com.hypixel.hytale.server.core.io.handlers.login

Файл: com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java

Поля (1)

МодификаторыТипИмя
REQUESTING_AUTH_GRANT, AWAITING_AUTH_TOKEN, PROCESSING_AUTH_TOKEN, EXCHANGING_SERVER_TOKEN, AUTHENTICATED

Исходный код

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

class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.protocol.HostAddress;
class="kw">import com.hypixel.hytale.protocol.PlayerSkin;
class="kw">import com.hypixel.hytale.protocol.ToServerPacket;
class="kw">import com.hypixel.hytale.protocol.io.ChannelConnection;
class="kw">import com.hypixel.hytale.protocol.io.ConnectionHandler;
class="kw">import com.hypixel.hytale.protocol.packets.auth.AuthGrant;
class="kw">import com.hypixel.hytale.protocol.packets.auth.AuthToken;
class="kw">import com.hypixel.hytale.protocol.packets.auth.ServerAuthToken;
class="kw">import com.hypixel.hytale.protocol.packets.connection.ClientDisconnect;
class="kw">import com.hypixel.hytale.protocol.packets.connection.ClientType;
class="kw">import com.hypixel.hytale.server.core.Constants;
class="kw">import com.hypixel.hytale.server.core.HytaleServer;
class="kw">import com.hypixel.hytale.server.core.HytaleServerConfig;
class="kw">import com.hypixel.hytale.server.core.Message;
class="kw">import com.hypixel.hytale.server.core.auth.AuthConfig;
class="kw">import com.hypixel.hytale.server.core.auth.JWTValidator;
class="kw">import com.hypixel.hytale.server.core.auth.PlayerAuthentication;
class="kw">import com.hypixel.hytale.server.core.auth.ServerAuthManager;
class="kw">import com.hypixel.hytale.server.core.auth.SessionServiceClient;
class="kw">import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule;
class="kw">import com.hypixel.hytale.server.core.io.ProtocolVersion;
class="kw">import com.hypixel.hytale.server.core.io.handlers.GenericConnectionPacketHandler;
class="kw">import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
class="kw">import java.security.SecureRandom;
class="kw">import java.security.cert.X509Certificate;
class="kw">import java.util.UUID;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;

class="kw">public class="kw">abstract class HandshakeHandler class="kw">extends GenericConnectionPacketHandler {
   class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
   class="kw">private class="kw">static class="kw">volatile SessionServiceClient sessionServiceClient;
   class="kw">private class="kw">static class="kw">volatile JWTValidator jwtValidator;
   class="kw">private class="kw">volatile HandshakeHandler.AuthState authState = HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT;
   class="kw">private class="kw">volatile boolean authTokenPacketReceived = false;
   class="kw">private class="kw">volatile String authenticatedUsername;
   class="kw">private class="kw">volatile PlayerSkin authenticatedSkin;
   class="kw">private class="kw">final ClientType clientType;
   class="kw">private class="kw">final String identityToken;
   class="kw">private class="kw">final byte[] referralData;
   class="kw">private class="kw">final HostAddress referralSource;
   @Nullable
   class="kw">private class="kw">volatile JWTValidator.IdentityTokenClaims identityClaims;

   class="kw">public HandshakeHandler(
      @Nonnull ChannelConnection var1,
      @Nonnull ProtocolVersion var2,
      @Nonnull String var3,
      @Nonnull ClientType var4,
      @Nonnull String var5,
      @Nullable byte[] var6,
      @Nullable HostAddress var7
   ) {
      super(var1, var2, var3);
      this.clientType = var4;
      this.identityToken = var5;
      this.referralData = var6;
      this.referralSource = var7;
   }

   class="kw">private class="kw">static SessionServiceClient getSessionServiceClient() {
      if (sessionServiceClient == null) {
         class="kw">synchronized (HandshakeHandler.class) {
            if (sessionServiceClient == null) {
               sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
            }
         }
      }

      class="kw">return sessionServiceClient;
   }

   class="kw">private class="kw">static JWTValidator getJwtValidator() {
      if (jwtValidator == null) {
         class="kw">synchronized (HandshakeHandler.class) {
            if (jwtValidator == null) {
               jwtValidator = new JWTValidator(getSessionServiceClient(), "https://sessions.hytale.com", AuthConfig.getServerAudience());
            }
         }
      }

      class="kw">return jwtValidator;
   }

   @Override
   class="kw">public void accept(@Nonnull ToServerPacket var1) {
      class="kw">switch (var1.getId()) {
         case 1:
            this.handle((ClientDisconnect)var1);
            break;
         case 12:
            this.handle((AuthToken)var1);
            break;
         class="kw">default:
            this.disconnect(Message.translation("client.general.disconnect.protocol.unexpectedPacket").param("packetId", var1.getId()));
      }
   }

   @Override
   class="kw">public void registered0(ConnectionHandler var1) {
      HytaleServerConfig.TimeoutProfile var2 = HytaleServer.get().getConfig().getConnectionTimeouts();
      this.enterStage("auth", var2.getAuth());
      this.identityClaims = getJwtValidator().validateIdentityToken(this.identityToken);
      if (this.identityClaims == null) {
         LOGGER.at(Level.WARNING).log("Identity token validation failed for %s", this.getChannel().formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      } else if (this.identityClaims.subject == null || this.identityClaims.subject.isEmpty()) {
         LOGGER.at(Level.WARNING).log("Identity token UUID missing for %s", this.getChannel().formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      } else if (this.identityClaims.username != null && !this.identityClaims.username.isEmpty()) {
         String var3 = this.clientType == ClientType.Editor ? "hytale:editor" : "hytale:client";
         if (!this.identityClaims.hasScope(var3)) {
            LOGGER.at(Level.WARNING)
               .log(
                  "Identity token missing required scope for %s from %s (clientType: %s, required: %s, actual: %s)",
                  this.identityClaims.username,
                  this.getChannel().formatRemoteAddress(),
                  this.clientType,
                  var3,
                  this.identityClaims.scope
               );
            this.disconnect(Message.translation("client.general.disconnect.identityTokenMissingScope").param("scope", var3));
         } else {
            LOGGER.at(Level.INFO)
               .log(
                  "Identity token validated for %s (UUID: %s, scope: %s) from %s, requesting auth grant",
                  this.identityClaims.username,
                  this.identityClaims.subject,
                  this.identityClaims.scope,
                  this.getChannel().formatRemoteAddress()
               );
            this.continueStage("auth:grant", var2.getAuthGrant(), () -> this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT);
            this.requestAuthGrant();
         }
      } else {
         LOGGER.at(Level.WARNING).log("Identity token username missing for %s", this.getChannel().formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      }
   }

   class="kw">private void requestAuthGrant() {
      String var1 = ServerAuthManager.getInstance().getSessionToken();
      if (var1 != null && !var1.isEmpty()) {
         ChannelConnection var2 = this.getChannel();
         getSessionServiceClient().requestAuthorizationGrantAsync(this.identityToken, AuthConfig.getServerAudience(), var1).thenAccept(var2x -> {
            if (var2.isActive()) {
               if (var2x == null) {
                  var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.authGrantFailed")));
               } else {
                  String var3 = ServerAuthManager.getInstance().getIdentityToken();
                  if (var3 != null && !var3.isEmpty()) {
                     String var4 = var3;
                     var2.execute(() -> {
                        if (var2.isActive()) {
                           if (this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT) {
                              LOGGER.at(Level.WARNING).log("State changed during auth grant request, current state: %s", this.authState);
                           } else {
                              this.clearTimeout();
                              LOGGER.at(Level.INFO).log("Sending AuthGrant to %s (with server identity: %s)", var2.formatRemoteAddress(), !var4.isEmpty());
                              this.write(new AuthGrant(var2x, var4));
                              this.authState = HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN;
                              HytaleServerConfig.TimeoutProfile var4 = HytaleServer.get().getConfig().getConnectionTimeouts();
                              this.continueStage("auth:token", var4.getAuthToken(), () -> this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN);
                           }
                        }
                     });
                  } else {
                     LOGGER.at(Level.SEVERE).log("Server identity token not available - cannot complete mutual authentication");
                     var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable")));
                  }
               }
            }
         }).exceptionally(var2x -> {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var2x)).log("Error requesting auth grant");
            var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.authError")));
            class="kw">return null;
         });
      } else {
         LOGGER.at(Level.SEVERE).log("Server session token not available - cannot request auth grant");
         this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
      }
   }

   class="kw">public void handle(@Nonnull ClientDisconnect var1) {
      this.disconnectReason.setClientDisconnectType(var1.type);
      if (this.identityClaims != null) {
         LOGGER.at(Level.INFO)
            .log(
               "%s (%s) at %s left with reason: %s - %s",
               this.identityClaims.subject,
               this.identityClaims.username,
               this.getChannel().formatRemoteAddress(),
               var1.type.name(),
               var1.reason.name()
            );
      }

      this.getChannel().closeApplicationConnection();
   }

   class="kw">public void handle(@Nonnull AuthToken var1) {
      ChannelConnection var2 = this.getChannel();
      if (this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN) {
         LOGGER.at(Level.WARNING).log("Received unexpected AuthToken packet in state %s from %s", this.authState, var2.formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.protocol.unexpectedAuthToken"));
      } else if (this.authTokenPacketReceived) {
         LOGGER.at(Level.WARNING).log("Received duplicate AuthToken packet from %s", var2.formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.protocol.duplicateAuthToken"));
      } else {
         this.authTokenPacketReceived = true;
         this.authState = HandshakeHandler.AuthState.PROCESSING_AUTH_TOKEN;
         this.clearTimeout();
         String var3 = var1.accessToken;
         if (var3 != null && !var3.isEmpty()) {
            String var4 = var1.serverAuthorizationGrant;
            X509Certificate var5 = var2.getClientCertificate();
            LOGGER.at(Level.INFO)
               .log(
                  "Received AuthToken from %s, validating JWT (mTLS cert present: %s, server auth grant: %s)",
                  var2.formatRemoteAddress(),
                  var5 != null,
                  var4 != null && !var4.isEmpty()
               );
            JWTValidator.JWTClaims var6 = getJwtValidator().validateToken(var3, var5);
            if (var6 == null) {
               LOGGER.at(Level.WARNING).log("JWT validation failed for %s", var2.formatRemoteAddress());
               this.disconnect(Message.translation("client.general.disconnect.invalidAccessToken"));
            } else if (this.identityClaims == null) {
               LOGGER.at(Level.WARNING).log("Identity token is null prior to validating authentication token for %s", this.getChannel().formatRemoteAddress());
               this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
            } else {
               String var7 = var6.subject;
               String var8 = var6.username;
               if (var7 == null || !var7.equals(this.identityClaims.subject)) {
                  LOGGER.at(Level.WARNING)
                     .log("JWT UUID mismatch for %s (expected: %s, got: %s)", this.getChannel().formatRemoteAddress(), this.identityClaims.subject, var7);
                  this.disconnect(Message.translation("client.general.disconnect.tokenUuidMismatch"));
               } else if (var8 == null || var8.isEmpty()) {
                  LOGGER.at(Level.WARNING).log("JWT missing username for %s", var2.formatRemoteAddress());
                  this.disconnect(Message.translation("client.general.disconnect.tokenMissingUsername"));
               } else if (!var8.equals(this.identityClaims.username)) {
                  LOGGER.at(Level.WARNING)
                     .log("JWT username mismatch for %s (expected: %s, got: %s)", this.getChannel().formatRemoteAddress(), this.identityClaims.username, var8);
                  this.disconnect(Message.translation("client.general.disconnect.tokenUsernameMismatch"));
               } else {
                  this.authenticatedUsername = var8;
                  if (this.identityClaims.skin != null && !this.identityClaims.skin.isEmpty()) {
                     try {
                        PlayerSkin var9 = CosmeticsModule.get().parseSkinFromJson(this.identityClaims.skin);
                        if (var9 == null) {
                           this.disconnect(Message.translation("client.general.disconnect.invalidSkin").param("details", "failed to parse skin data"));
                           class="kw">return;
                        }

                        CosmeticsModule.get().validateSkin(var9);
                        this.authenticatedSkin = var9;
                     } catch (CosmeticsModule.InvalidSkinException var10) {
                        this.disconnect(Message.translation("client.general.disconnect.invalidSkin").param("details", var10.getMessage()));
                        class="kw">return;
                     }
                  }

                  if (var4 != null && !var4.isEmpty()) {
                     this.authState = HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN;
                     HytaleServerConfig.TimeoutProfile var11 = HytaleServer.get().getConfig().getConnectionTimeouts();
                     this.continueStage(
                        "auth:server-exchange", var11.getAuthServerExchange(), () -> this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN
                     );
                     this.exchangeServerAuthGrant(var4);
                  } else {
                     LOGGER.at(Level.WARNING).log("Client did not provide server auth grant for mutual authentication");
                     this.disconnect(Message.translation("client.general.disconnect.mutualAuthRequired"));
                  }
               }
            }
         } else {
            LOGGER.at(Level.WARNING).log("Received AuthToken packet with empty access token from %s", var2.formatRemoteAddress());
            this.disconnect(Message.translation("client.general.disconnect.invalidAccessToken"));
         }
      }
   }

   class="kw">private void exchangeServerAuthGrant(@Nonnull String var1) {
      ServerAuthManager var2 = ServerAuthManager.getInstance();
      String var3 = var2.getServerCertificateFingerprint();
      if (var3 == null) {
         LOGGER.at(Level.SEVERE).log("Server certificate fingerprint not available for mutual auth");
         this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
      } else {
         String var4 = var2.getSessionToken();
         LOGGER.at(Level.FINE).log("Server session token available: %s, identity token available: %s", var4 != null, var2.getIdentityToken() != null);
         if (var4 == null) {
            LOGGER.at(Level.SEVERE).log("Server session token not available for auth grant exchange");
            LOGGER.at(Level.FINE)
               .log("Auth mode: %s, has session token: %s, has identity token: %s", var2.getAuthStatus(), var2.hasSessionToken(), var2.hasIdentityToken());
            this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
         } else {
            LOGGER.at(Level.FINE).log("Using session token (first 20 chars): %s...", var4.length() > 20 ? var4.substring(0, 20) : var4);
            ChannelConnection var5 = this.getChannel();
            getSessionServiceClient().exchangeAuthGrantForTokenAsync(var1, var3, var4).thenAccept(var2x -> {
               if (var5.isActive()) {
                  var5.execute(() -> {
                     if (var5.isActive()) {
                        if (this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) {
                           LOGGER.at(Level.WARNING).log("State changed during server token exchange, current state: %s", this.authState);
                        } else if (var2x == null) {
                           LOGGER.at(Level.SEVERE).log("Failed to exchange server auth grant for access token");
                           this.disconnect(Message.translation("client.general.disconnect.serverAuthFailed"));
                        } else {
                           byte[] var3 = this.generatePasswordChallengeIfNeeded();
                           LOGGER.at(Level.INFO).log("Sending ServerAuthToken to %s (with password challenge: %s)", var5.formatRemoteAddress(), var3 != null);
                           this.write(new ServerAuthToken(var2x, var3));
                           this.completeAuthentication(var3);
                        }
                     }
                  });
               }
            }).exceptionally(var2x -> {
               ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var2x)).log("Error exchanging server auth grant");
               var5.execute(() -> {
                  if (this.authState == HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) {
                     this.disconnect(Message.translation("client.general.disconnect.serverAuthFailed"));
                  }
               });
               class="kw">return null;
            });
         }
      }
   }

   class="kw">private byte[] generatePasswordChallengeIfNeeded() {
      String var1 = HytaleServer.get().getConfig().getPassword();
      if (var1 != null && !var1.isEmpty()) {
         if (Constants.SINGLEPLAYER) {
            UUID var2 = SingleplayerModule.getUuid();
            if (var2 != null && var2.equals(this.identityClaims.getSubjectAsUUID())) {
               class="kw">return null;
            }
         }

         byte[] var3 = new byte[32];
         new SecureRandom().nextBytes(var3);
         class="kw">return var3;
      } else {
         class="kw">return null;
      }
   }

   class="kw">private void completeAuthentication(byte[] var1) {
      UUID var2 = this.identityClaims.getSubjectAsUUID();
      if (var2 == null) {
         LOGGER.at(Level.SEVERE).log("Identity token subject is not a valid UUID: %s", this.identityClaims.subject);
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      } else {
         this.auth = new PlayerAuthentication(var2, this.authenticatedUsername);
         if (this.referralData != null) {
            this.auth.setReferralData(this.referralData);
         }

         if (this.referralSource != null) {
            this.auth.setReferralSource(this.referralSource);
         }

         if (this.authenticatedSkin != null) {
            this.auth.setSkin(this.authenticatedSkin);
         }

         this.authState = HandshakeHandler.AuthState.AUTHENTICATED;
         this.clearTimeout();
         LOGGER.at(Level.INFO)
            .log(
               "Mutual authentication complete for %s (%s) from %s",
               this.authenticatedUsername,
               this.identityClaims.subject,
               this.getChannel().formatRemoteAddress()
            );
         this.onAuthenticated(var1);
      }
   }

   class="kw">protected class="kw">abstract void onAuthenticated(byte[] var1);

   class="kw">private enum AuthState {
      REQUESTING_AUTH_GRANT,
      AWAITING_AUTH_TOKEN,
      PROCESSING_AUTH_TOKEN,
      EXCHANGING_SERVER_TOKEN,
      AUTHENTICATED;

      AuthState() {
      }
   }
}