AuthResult enum
Пакет: com.hypixel.hytale.server.core.auth
Файл: com/hypixel/hytale/server/core/auth/ServerAuthManager.java
Поля (1)
| Модификаторы | Тип | Имя |
|---|---|---|
|
SUCCESS,
PENDING_PROFILE_SELECTION, |
FAILED |
Исходный код
Показать/скрыть
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() {
}
}
}