OAuthClient class

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

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

Поля (78)

МодификаторыТипИмя
static final BuilderCodec<OAuthClient.DeviceAuthResponseData> CODEC
static final BuilderCodec<OAuthClient.TokenResponseData> CODEC
final HytaleLogger LOGGER
String accessToken
String deviceCode
String error
int expiresIn
int expiresIn
String idToken
int interval
String refreshToken
return
return
return
return
return
return
String userCode
String var1
String var1
CompletableFuture var10
String var10
HttpServer var11
String var11
String var12
String var13
String var14
OAuthClient.TokenResponse var15
AtomicBoolean var2
AtomicBoolean var2
String var2
HttpRequest var2
String var2
byte[] var2
MessageDigest var2
OAuthClient.TokenResponseData var2
OAuthClient.DeviceAuthResponseData var2
HttpServer var3
var3
OAuthClient.DeviceAuthResponse var3
HttpRequest var3
HttpResponse var3
HttpRequest var3
byte[] var3
Pattern var3
String var3
String var4
int var4
HttpResponse var4
String var4
HttpResponse var4
Matcher var4
String var5
String var5
long var5
int var5
HttpRequest var5
String var5
String var6
String var6
HttpResponse var6
String var6
int var7
String var7
OAuthClient.TokenResponse var7
String var7
String var8
String var8
var8
var8
var8
String var9
int var9
var9
var9
var9
String verificationUri
String verificationUriComplete

Методы (30)

МодификаторыВозвратСигнатура
public record DeviceAuthResponserecord DeviceAuthResponse(String deviceCode, String userCode, String verificationUri, String verificationUriComplete, int expiresIn, int interval)
private DeviceAuthResponseDataprivate DeviceAuthResponseData()
abstract throw new RuntimeExceptionthrow new RuntimeException("Failed to generate code challenge", var4)
public record TokenResponserecord TokenResponse(@Nullable String accessToken, @Nullable String refreshToken, @Nullable String idToken, @Nullable String error, int expiresIn)
private TokenResponseDataprivate TokenResponseData()
private String buildAuthUrlString buildAuthUrl(String var1, String var2, String var3)
static String buildHtmlPageString buildHtmlPage(boolean var0, String var1, String var2, String var3, @Nullable String var4)
private String encodeStateWithPortString encodeStateWithPort(String var1, int var2)
static <T> KeyedCodec<T> externalKey<T> KeyedCodec<T> externalKey(String var0, Codec<T> var1)
private String extractParamString extractParam(String var1, String var2)
static String formatVerificationCodeString formatVerificationCode(String var0)
private String generateCodeChallengeString generateCodeChallenge(String var1)
private String generateRandomStringString generateRandomString(int var1)
if if(var15 != null)
if if(var19 instanceof InterruptedException)
if if(var3 != null)
if if(var3 == null)
if if(var7 != null)
if if(var7.error == null)
if if(var8 instanceof InterruptedException)
if if(var5 != 200)
if if(var7 instanceof InterruptedException)
if if(var4 instanceof InterruptedException)
if if(var5 instanceof InterruptedException)
if if(var1 == null)
public boolean isSuccesspublic boolean isSuccess()
private OAuthClient.DeviceAuthResponse parseDeviceAuthResponseOAuthClient.DeviceAuthResponse parseDeviceAuthResponse(String var1)
private OAuthClient.TokenResponse parseTokenResponseOAuthClient.TokenResponse parseTokenResponse(String var1)
public Runnable startFlowRunnable startFlow(@Nonnull OAuthBrowserFlow var1)
public Runnable startFlowRunnable startFlow(OAuthDeviceFlow var1)

Исходный код

Показать/скрыть
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() {
      }
   }
}