TokenResponseData class

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

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

Поля (6)

МодификаторыТипИмя
static final BuilderCodec<OAuthClient.TokenResponseData> CODEC
String accessToken
String error
int expiresIn
String idToken
String refreshToken

Исходный код

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

class="kw">import com.hypixel.hytale.codec.Codec;
class="kw">import com.hypixel.hytale.codec.EmptyExtraInfo;
class="kw">import com.hypixel.hytale.codec.KeyedCodec;
class="kw">import com.hypixel.hytale.codec.builder.BuilderCodec;
class="kw">import com.hypixel.hytale.codec.util.RawJsonReader;
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.auth.AuthConfig;
class="kw">import com.hypixel.hytale.server.core.auth.HttpResponseException;
class="kw">import com.hypixel.hytale.server.core.util.ServiceHttpClientFactory;
class="kw">import com.sun.net.httpserver.HttpServer;
class="kw">import java.io.IOException;
class="kw">import java.io.OutputStream;
class="kw">import java.net.InetAddress;
class="kw">import java.net.InetSocketAddress;
class="kw">import java.net.URI;
class="kw">import java.net.URLDecoder;
class="kw">import java.net.URLEncoder;
class="kw">import java.net.http.HttpClient;
class="kw">import java.net.http.HttpRequest;
class="kw">import java.net.http.HttpResponse;
class="kw">import java.net.http.HttpRequest.BodyPublishers;
class="kw">import java.net.http.HttpResponse.BodyHandlers;
class="kw">import java.nio.charset.StandardCharsets;
class="kw">import java.security.MessageDigest;
class="kw">import java.security.SecureRandom;
class="kw">import java.time.Duration;
class="kw">import java.util.Base64;
class="kw">import java.util.concurrent.CompletableFuture;
class="kw">import java.util.concurrent.TimeUnit;
class="kw">import java.util.concurrent.atomic.AtomicBoolean;
class="kw">import java.util.logging.Level;
class="kw">import java.util.regex.Matcher;
class="kw">import java.util.regex.Pattern;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;

class="kw">public class OAuthClient {
   class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
   class="kw">private class="kw">static class="kw">final SecureRandom RANDOM = new SecureRandom();
   class="kw">private class="kw">final HttpClient httpClient = ServiceHttpClientFactory.create(AuthConfig.HTTP_TIMEOUT);

   class="kw">public OAuthClient() {
   }

   class="kw">public Runnable startFlow(@Nonnull OAuthBrowserFlow var1) {
      AtomicBoolean var2 = new AtomicBoolean(false);
      CompletableFuture.runAsync(
         () -> {
            HttpServer var3 = null;

            try {
               String var4 = this.generateRandomString(32);
               String var5 = this.generateRandomString(64);
               String var6 = this.generateCodeChallenge(var5);
               var3 = HttpServer.create(new InetSocketAddress(InetAddress.ofLiteral("127.0.0.1"), 0), 0);
               int var7 = var3.getAddress().getPort();
               String var8 = this.encodeStateWithPort(var4, var7);
               String var9 = "https://accounts.hytale.com/consent/client";
               CompletableFuture var10 = new CompletableFuture<>();
               HttpServer var11 = var3;
               String var12 = var4;
               var3.createContext(
                  "/",
                  var4x -> {
                     try {
                        String var5 = var4x.getRequestURI().getQuery();
                        String var6 = this.extractParam(var5, "code");
                        String var7 = this.extractParam(var5, "state");
                        String var8;
                        int var9;
                        if (var7 == null || !var7.equals(var12)) {
                           var8 = buildHtmlPage(
                              false,
                              "Authentication Failed",
                              "Authentication Failed",
                              "Something went wrong during authentication. Please close this window and try again.",
                              "Invalid state parameter"
                           );
                           var9 = 400;
                           var10.completeExceptionally(new Exception("Invalid state"));
                        } else if (var6 != null && !var6.isEmpty()) {
                           var8 = buildHtmlPage(
                              true,
                              "Authentication Successful",
                              "Authentication Successful",
                              "You have been logged in successfully. You can now close this window and class="kw">return to the server.",
                              null
                           );
                           var9 = 200;
                           var10.complete(var6);
                        } else {
                           String var10 = this.extractParam(var5, "error");
                           String var11 = var10 != null ? var10 : "No code received";
                           var8 = buildHtmlPage(
                              false,
                              "Authentication Failed",
                              "Authentication Failed",
                              "Something went wrong during authentication. Please close this window and try again.",
                              var11
                           );
                           var9 = 400;
                           var10.completeExceptionally(new Exception(var11));
                        }

                        var4x.sendResponseHeaders(var9, var8.length());

                        try (OutputStream var22 = var4x.getResponseBody()) {
                           var22.write(var8.getBytes(StandardCharsets.UTF_8));
                        }
                     } catch (Exception var20) {
                        ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var20)).log("Error handling OAuth callback");
                     } class="kw">finally {
                        HytaleServer.SCHEDULED_EXECUTOR.schedule(() -> var11.stop(0), 1L, TimeUnit.SECONDS);
                     }
                  }
               );
               var3.setExecutor(null);
               var3.start();
               String var13 = this.buildAuthUrl(var8, var6, var9);
               var1.onFlowInfo(var13, formatVerificationCode(var6));
               String var14 = var10.get(5L, TimeUnit.MINUTES);
               if (var2.get()) {
                  var1.onFailure("Authentication cancelled");
                  class="kw">return;
               }

               OAuthClient.TokenResponse var15 = this.exchangeCodeForTokens(var14, var5, var9);
               if (var15 != null) {
                  var1.onSuccess(var15);
                  class="kw">return;
               }

               var1.onFailure("Failed to exchange authorization code for tokens");
            } catch (Exception var19) {
               if (var19 class="kw">instanceof InterruptedException) {
                  Thread.currentThread().interrupt();
               }

               ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var19)).log("OAuth browser flow failed");
               if (!var2.get()) {
                  var1.onFailure(var19.getMessage());
               }

               class="kw">return;
            } class="kw">finally {
               if (var3 != null) {
                  var3.stop(0);
               }
            }
         }
      );
      class="kw">return () -> var2.set(true);
   }

   class="kw">public Runnable startFlow(OAuthDeviceFlow var1) {
      AtomicBoolean var2 = new AtomicBoolean(false);
      CompletableFuture.runAsync(() -> {
         try {
            OAuthClient.DeviceAuthResponse var3 = this.requestDeviceAuthorization();
            if (var3 == null) {
               var1.onFailure("Failed to start device authorization");
               class="kw">return;
            }

            var1.onFlowInfo(var3.userCode(), var3.verificationUri(), var3.verificationUriComplete(), var3.expiresIn());
            int var4 = Math.max(var3.interval, 15);
            long var5 = System.currentTimeMillis() + var3.expiresIn * 1000L;

            while (System.currentTimeMillis() < var5 && !var2.get()) {
               Thread.sleep(Duration.ofSeconds(var4));
               OAuthClient.TokenResponse var7 = this.pollDeviceToken(var3.deviceCode);
               if (var7 != null) {
                  if (var7.error == null) {
                     var1.onSuccess(var7);
                     class="kw">return;
                  }

                  if (!"authorization_pending".equals(var7.error)) {
                     if (!"slow_down".equals(var7.error)) {
                        var1.onFailure("Device authorization failed: " + var7.error);
                        class="kw">return;
                     }

                     var4 += 5;
                  }
               }
            }

            if (var2.get()) {
               var1.onFailure("Authentication cancelled");
            } else {
               var1.onFailure("Device authorization expired");
            }
         } catch (Exception var8) {
            if (var8 class="kw">instanceof InterruptedException) {
               Thread.currentThread().interrupt();
            }

            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var8)).log("OAuth device flow failed");
            if (!var2.get()) {
               var1.onFailure(var8.getMessage());
            }
         }
      });
      class="kw">return () -> var2.set(true);
   }

   @Nullable
   class="kw">public OAuthClient.TokenResponse refreshTokens(@Nonnull String var1) class="kw">throws IOException, InterruptedException {
      try {
         String var2 = "grant_type=refresh_token&client_id="
            + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8)
            + "&refresh_token="
            + URLEncoder.encode(var1, StandardCharsets.UTF_8);
         HttpRequest var3 = HttpRequest.newBuilder()
            .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/token"))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .header("User-Agent", AuthConfig.USER_AGENT)
            .POST(BodyPublishers.ofString(var2))
            .build();
         HttpResponse var4 = this.httpClient.send(var3, BodyHandlers.ofString());
         int var5 = var4.statusCode();
         if (var5 != 200) {
            LOGGER.at(Level.WARNING).log("Token refresh failed: HTTP %d - %s", var5, var4.body());
            if (!AuthConfig.isRejectedStatusCode(var5)) {
               throw new HttpResponseException(var5, var4.body());
            } else {
               class="kw">return null;
            }
         } else {
            class="kw">return this.parseTokenResponse(var4.body());
         }
      } catch (IOException var6) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var6)).log("Token refresh failed (IO error)");
         throw var6;
      } catch (InterruptedException var7) {
         Thread.currentThread().interrupt();
         throw var7;
      } catch (Exception var8) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var8)).log("Token refresh failed");
         class="kw">return null;
      }
   }

   class="kw">private String buildAuthUrl(String var1, String var2, String var3) {
      class="kw">return "https://oauth.accounts.hytale.com/oauth2/auth?response_type=code&client_id="
         + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8)
         + "&redirect_uri="
         + URLEncoder.encode(var3, StandardCharsets.UTF_8)
         + "&scope="
         + URLEncoder.encode(String.join(" ", AuthConfig.SCOPES), StandardCharsets.UTF_8)
         + "&state="
         + URLEncoder.encode(var1, StandardCharsets.UTF_8)
         + "&code_challenge="
         + URLEncoder.encode(var2, StandardCharsets.UTF_8)
         + "&code_challenge_method=S256";
   }

   class="kw">private class="kw">static String formatVerificationCode(String var0) {
      String var1 = var0.replaceAll("[^A-Za-z0-9]", "");
      class="kw">return var1.length() < 8 ? var1 : var1.substring(0, 4) + "-" + var1.substring(4, 8);
   }

   @Nullable
   class="kw">private OAuthClient.TokenResponse exchangeCodeForTokens(String var1, String var2, String var3) {
      try {
         String var4 = "grant_type=authorization_code&client_id="
            + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8)
            + "&code="
            + URLEncoder.encode(var1, StandardCharsets.UTF_8)
            + "&redirect_uri="
            + URLEncoder.encode(var3, StandardCharsets.UTF_8)
            + "&code_verifier="
            + URLEncoder.encode(var2, StandardCharsets.UTF_8);
         HttpRequest var5 = HttpRequest.newBuilder()
            .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/token"))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .header("User-Agent", AuthConfig.USER_AGENT)
            .POST(BodyPublishers.ofString(var4))
            .build();
         HttpResponse var6 = this.httpClient.send(var5, BodyHandlers.ofString());
         if (var6.statusCode() != 200) {
            LOGGER.at(Level.WARNING).log("Token exchange failed: HTTP %d - %s", var6.statusCode(), var6.body());
            class="kw">return null;
         } else {
            class="kw">return this.parseTokenResponse(var6.body());
         }
      } catch (Exception var7) {
         if (var7 class="kw">instanceof InterruptedException) {
            Thread.currentThread().interrupt();
         }

         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var7)).log("Token exchange failed");
         class="kw">return null;
      }
   }

   @Nullable
   class="kw">private OAuthClient.DeviceAuthResponse requestDeviceAuthorization() {
      try {
         String var1 = "client_id="
            + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8)
            + "&scope="
            + URLEncoder.encode(String.join(" ", AuthConfig.SCOPES), StandardCharsets.UTF_8);
         HttpRequest var2 = HttpRequest.newBuilder()
            .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/device/auth"))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .header("User-Agent", AuthConfig.USER_AGENT)
            .POST(BodyPublishers.ofString(var1))
            .build();
         HttpResponse var3 = this.httpClient.send(var2, BodyHandlers.ofString());
         if (var3.statusCode() != 200) {
            LOGGER.at(Level.WARNING).log("Device authorization request failed: HTTP %d - %s", var3.statusCode(), var3.body());
            class="kw">return null;
         } else {
            class="kw">return this.parseDeviceAuthResponse(var3.body());
         }
      } catch (Exception var4) {
         if (var4 class="kw">instanceof InterruptedException) {
            Thread.currentThread().interrupt();
         }

         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Device authorization request failed");
         class="kw">return null;
      }
   }

   @Nullable
   class="kw">private OAuthClient.TokenResponse pollDeviceToken(String var1) {
      try {
         String var2 = "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id="
            + URLEncoder.encode("hytale-server", StandardCharsets.UTF_8)
            + "&device_code="
            + URLEncoder.encode(var1, StandardCharsets.UTF_8);
         HttpRequest var3 = HttpRequest.newBuilder()
            .uri(URI.create("https://oauth.accounts.hytale.com/oauth2/token"))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .header("User-Agent", AuthConfig.USER_AGENT)
            .POST(BodyPublishers.ofString(var2))
            .build();
         HttpResponse var4 = this.httpClient.send(var3, BodyHandlers.ofString());
         if (var4.statusCode() == 400) {
            class="kw">return this.parseTokenResponse(var4.body());
         } else if (var4.statusCode() != 200) {
            LOGGER.at(Level.WARNING).log("Device token poll failed: HTTP %d - %s", var4.statusCode(), var4.body());
            class="kw">return null;
         } else {
            class="kw">return this.parseTokenResponse(var4.body());
         }
      } catch (Exception var5) {
         if (var5 class="kw">instanceof InterruptedException) {
            Thread.currentThread().interrupt();
         }

         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var5)).log("Device token poll failed");
         class="kw">return null;
      }
   }

   class="kw">private String generateRandomString(int var1) {
      byte[] var2 = new byte[var1];
      RANDOM.nextBytes(var2);
      class="kw">return Base64.getUrlEncoder().withoutPadding().encodeToString(var2).substring(0, var1);
   }

   class="kw">private String generateCodeChallenge(String var1) {
      try {
         MessageDigest var2 = MessageDigest.getInstance("SHA-256");
         byte[] var3 = var2.digest(var1.getBytes(StandardCharsets.US_ASCII));
         class="kw">return Base64.getUrlEncoder().withoutPadding().encodeToString(var3);
      } catch (Exception var4) {
         throw new RuntimeException("Failed to generate code challenge", var4);
      }
   }

   class="kw">private String extractParam(String var1, String var2) {
      if (var1 == null) {
         class="kw">return null;
      }

      Pattern var3 = Pattern.compile(var2 + "=([^&]*)");
      Matcher var4 = var3.matcher(var1);
      class="kw">return var4.find() ? URLDecoder.decode(var4.group(1), StandardCharsets.UTF_8) : null;
   }

   class="kw">private String encodeStateWithPort(String var1, int var2) {
      String var3 = String.format("{\"state\":\"%s\",\"port\":\"%d\"}", var1, var2);
      class="kw">return Base64.getUrlEncoder().withoutPadding().encodeToString(var3.getBytes(StandardCharsets.UTF_8));
   }

   class="kw">private OAuthClient.TokenResponse parseTokenResponse(String var1) class="kw">throws IOException {
      OAuthClient.TokenResponseData var2 = OAuthClient.TokenResponseData.CODEC.decodeJson(new RawJsonReader(var1.toCharArray()), EmptyExtraInfo.EMPTY);
      class="kw">return new OAuthClient.TokenResponse(var2.accessToken, var2.refreshToken, var2.idToken, var2.error, var2.expiresIn);
   }

   class="kw">private OAuthClient.DeviceAuthResponse parseDeviceAuthResponse(String var1) class="kw">throws IOException {
      OAuthClient.DeviceAuthResponseData var2 = OAuthClient.DeviceAuthResponseData.CODEC
         .decodeJson(new RawJsonReader(var1.toCharArray()), EmptyExtraInfo.EMPTY);
      class="kw">return new OAuthClient.DeviceAuthResponse(
         var2.deviceCode, var2.userCode, var2.verificationUri, var2.verificationUriComplete, var2.expiresIn, var2.interval
      );
   }

   class="kw">private class="kw">static String buildHtmlPage(boolean var0, String var1, String var2, String var3, @Nullable String var4) {
      String var5 = var4 != null && !var4.isEmpty() ? "<div class=\"error\">" + var4 + "</div>" : "";
      String var6 = var0 ? "icon-success" : "icon-error";
      String var7 = var0
         ? "<polyline points=\"20 6 9 17 4 12\"></polyline>"
         : "<line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>";
      class="kw">return "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>%s - Hytale</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n    <link href=\"https://fonts.googleapis.com/css2?family=Lexend:wght@700&family=Nunito+Sans:wght@400;700&display=swap\" rel=\"stylesheet\">\n    <style>\n        * { margin: 0; padding: 0; box-sizing: border-box; }\n        html { color-scheme: dark; background: linear-gradient(180deg, #15243A, #0F1418); min-height: 100vh; }\n        body { font-family: \"Nunito Sans\", sans-serif; color: #b7cedd; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }\n        .card { background: rgba(0,0,0,0.4); border: 2px solid rgba(71,81,107,0.6); border-radius: 12px; padding: 48px 40px; max-width: 420px; text-align: center; }\n        .icon { width: 64px; height: 64px; margin: 0 auto 24px; border-radius: 50%%; display: flex; align-items: center; justify-content: center; }\n        .icon svg { width: 32px; height: 32px; }\n        .icon-success { background: linear-gradient(135deg, #2d5a3d, #1e3a2a); border: 2px solid #4a9d6b; }\n        .icon-success svg { color: #6fcf97; }\n        .icon-error { background: linear-gradient(135deg, #5a2d3d, #3a1e2a); border: 2px solid #c3194c; }\n        .icon-error svg { color: #ff6b8a; }\n        h1 { font-family: \"Lexend\", sans-serif; font-size: 1.5rem; text-transform: uppercase; background: linear-gradient(#f5fbff, #bfe6ff); -webkit-background-clip: text; background-clip: text; color: transparent; margin-bottom: 12px; }\n        p { line-height: 1.6; }\n        .error { background: rgba(195,25,76,0.15); border: 1px solid rgba(195,25,76,0.4); border-radius: 6px; padding: 12px; margin-top: 16px; color: #ff8fa8; font-size: 0.875rem; word-break: break-word; }\n    </style>\n</head>\n<body><div class=\"card\"><div class=\"icon %s\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">%s</svg></div><h1>%s</h1><p>%s</p>%s</div></body>\n</html>\n"
         .formatted(var1, var6, var7, var2, var3, var5);
   }

   class="kw">private class="kw">static <T> KeyedCodec<T> externalKey(String var0, Codec<T> var1) {
      class="kw">return new KeyedCodec<>(var0, var1, false, true);
   }

   class="kw">public record DeviceAuthResponse(String deviceCode, String userCode, String verificationUri, String verificationUriComplete, int expiresIn, int interval) {
      class="kw">public DeviceAuthResponse {
      }
   }

   class="kw">private class="kw">static class="kw">final class DeviceAuthResponseData {
      String deviceCode;
      String userCode;
      String verificationUri;
      String verificationUriComplete;
      int expiresIn = 600;
      int interval = 5;
      class="kw">static class="kw">final BuilderCodec<OAuthClient.DeviceAuthResponseData> CODEC = BuilderCodec.builder(
            OAuthClient.DeviceAuthResponseData.class, OAuthClient.DeviceAuthResponseData::new
         )
         .append(OAuthClient.externalKey("device_code", Codec.STRING), (var0, var1) -> var0.deviceCode = var1, var0 -> var0.deviceCode)
         .add()
         .append(OAuthClient.externalKey("user_code", Codec.STRING), (var0, var1) -> var0.userCode = var1, var0 -> var0.userCode)
         .add()
         .append(OAuthClient.externalKey("verification_uri", Codec.STRING), (var0, var1) -> var0.verificationUri = var1, var0 -> var0.verificationUri)
         .add()
         .append(
            OAuthClient.externalKey("verification_uri_complete", Codec.STRING),
            (var0, var1) -> var0.verificationUriComplete = var1,
            var0 -> var0.verificationUriComplete
         )
         .add()
         .append(OAuthClient.externalKey("expires_in", Codec.INTEGER), (var0, var1) -> var0.expiresIn = var1, var0 -> var0.expiresIn)
         .add()
         .append(OAuthClient.externalKey("interval", Codec.INTEGER), (var0, var1) -> var0.interval = var1, var0 -> var0.interval)
         .add()
         .build();

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

   class="kw">public record TokenResponse(@Nullable String accessToken, @Nullable String refreshToken, @Nullable String idToken, @Nullable String error, int expiresIn) {
      class="kw">public TokenResponse {
      }

      class="kw">public boolean isSuccess() {
         class="kw">return this.error == null && this.accessToken != null;
      }
   }

   class="kw">private class="kw">static class="kw">final class TokenResponseData {
      String accessToken;
      String refreshToken;
      String idToken;
      String error;
      int expiresIn;
      class="kw">static class="kw">final BuilderCodec<OAuthClient.TokenResponseData> CODEC = BuilderCodec.builder(
            OAuthClient.TokenResponseData.class, OAuthClient.TokenResponseData::new
         )
         .append(OAuthClient.externalKey("access_token", Codec.STRING), (var0, var1) -> var0.accessToken = var1, var0 -> var0.accessToken)
         .add()
         .append(OAuthClient.externalKey("refresh_token", Codec.STRING), (var0, var1) -> var0.refreshToken = var1, var0 -> var0.refreshToken)
         .add()
         .append(OAuthClient.externalKey("id_token", Codec.STRING), (var0, var1) -> var0.idToken = var1, var0 -> var0.idToken)
         .add()
         .append(OAuthClient.externalKey("error", Codec.STRING), (var0, var1) -> var0.error = var1, var0 -> var0.error)
         .add()
         .append(OAuthClient.externalKey("expires_in", Codec.INTEGER), (var0, var1) -> var0.expiresIn = var1, var0 -> var0.expiresIn)
         .add()
         .build();

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