Class CloudAuthManager

java.lang.Object
com.ultikits.ultitools.utils.CloudAuthManager

@Internal public class CloudAuthManager extends Object
The three command-facing entry points behind /ulticloud login|logout|status.

As of plan 16-09 (D-17) this class owns no state of its own and exposes no generation-shaped or TokenEntity-shaped public surface -- everything that used to be a fine-grained public static (the generation triple, saveToken/clearToken/refreshToken/ loadSavedToken, the start/stop pairs for polling and refresh) is gone outright, not deprecated: CloudSession instance methods now carry that behaviour, reached only through login(Runnable, Consumer, Runnable, Consumer, Consumer), logout() and status() below. Keeping a delegating shim for any of them would leave the generation convention callable, which is exactly what issue #298's second acceptance criterion requires to stop being true (see plan 16-08's CloudSession javadoc for the mechanism this replaced).

CredentialStaticSurfaceInvariantTest enforces this structurally: no public static method anywhere in this package may accept or return a TokenEntity or a generation (D-18), so a static bypass of the session's credential commit cannot be reintroduced unnoticed. As of 6.3.1 the 6.3.0 user token is neither stored nor sent (plan 22-24 removed its store); the server credential is committed only through CloudSession.commitCredential(ServerCredential).

Marked ApiStatus.Internal -- this class exists to serve CloudLoginCommand's three @CmdMapping methods and is not part of the framework's public API surface for module authors.

  • Field Details

    • MAX_SUGGESTED_NAME_CODE_POINTS

      static final int MAX_SUGGESTED_NAME_CODE_POINTS
      The longest suggestedName UltiCloud accepts, in code points (contract answer 13).
      See Also:
    • LOGIN_IN_PROGRESS

      static final String LOGIN_IN_PROGRESS
      The message a login reports when an overlapping login already replaced its session.
      See Also:
    • RECONNECT_COOLDOWN_MS

      private static final long RECONNECT_COOLDOWN_MS
      See Also:
  • Constructor Details

    • CloudAuthManager

      private CloudAuthManager()
  • Method Details

    • login

      public static void login(CloudAuthManager.DeviceLoginListener listener)
      Links this server to UltiCloud with the RFC 8628 device authorization grant (protocol v2).

      Runs the already-linked check (a credential held by the current session or stored in credentials.json), the rate-limit check, then replaces the current session and returns. The device-code request and the polling run on the new session's own poll executor, so a caller on the server's primary thread is never blocked on the network; their outcome reaches listener on that executor's thread.

      The lock structure is the one the 6.3.0 magic-link login used, unchanged: CloudSession.class, then the captured session's monitor, the re-check, the sessionAtCheck != CloudSession.current() guard, disableCloud(sessionAtCheck), startNew() -- the documented lock order of CloudSession's class javadoc.

      Parameters:
      listener - receives every outcome; must not be null
      Since:
      6.3.1
    • linkedCredential

      static ServerCredential linkedCredential(CloudSession session)
      The current credential for status, login and logout. The session is its one owner (plan 22-21 fix): every change to the stored entry -- device login, rotation, logout, rejection -- updates the session's copy in the same step, so the two never diverge. The file is read only when the session holds no credential (it has not adopted one yet, for example while cloud features are off at startup), so logout still revokes and deletes a stored credential the running session never loaded.
      Returns:
      the credential session holds, else the one stored in credentials.json, else null
    • runningFrameworkVersion

      static String runningFrameworkVersion()
      Returns:
      the running framework version, sent verbatim (a -SNAPSHOT suffix included): the plugin description's version, else env.yml's, else unknown
    • suggestedServerName

      static String suggestedServerName()
      Returns:
      the server's MOTD as plain text, at most 64 code points, or null when there is no server or the MOTD is blank (the field is then omitted; the owner names the server on the confirmation page either way)
    • normaliseSuggestedName

      static String normaliseSuggestedName(String motd)
      Parameters:
      motd - a legacy-formatted MOTD, or null
      Returns:
      the MOTD without colour codes, with every run of whitespace or control characters collapsed to one space, trimmed and cut to 64 code points without splitting a surrogate pair; null when nothing remains
    • storedLegacyServerUuid

      static String storedLegacyServerUuid()
      Returns:
      this server's stored install id (uuid in credentials.json) normalised by LegacyServerId.canonical(String), or null; reads only, never writes
    • login

      @Deprecated(since="6.3.1", forRemoval=true) public static void login(Runnable onAlreadyLoggedIn, Consumer<Long> onRateLimited, Runnable onRequesting, Consumer<String> onSuccess, Consumer<String> onError)
      Deprecated, for removal: This API element is subject to removal in a future version.
      Use login(DeviceLoginListener) instead; removed in 6.4.0.
      The 6.3.0 callback form of login(DeviceLoginListener), kept with its signature for compatibility. It now runs the device authorization: onSuccess receives the verification URI with the code filled in (verificationUriComplete); the later outcome (linked, denied, expired) is reported only in the server log.
      Parameters:
      onAlreadyLoggedIn - invoked, with nothing else attempted, if this server is already linked
      onRateLimited - invoked with the remaining cooldown in seconds if the login attempt is currently rate-limited
      onRequesting - invoked right before the device-code request is made
      onSuccess - invoked with the complete verification URI once a code was issued
      onError - invoked with an error message if the request fails
      Scheduled removal version:
      6.4.0
    • logout

      public static boolean logout() throws IOException
      Tears down cloud features unconditionally, then asynchronously revokes and deletes the protocol v2 credential. The revoke and delete finish after this method returns; use logoutReporting(Consumer) to observe their outcome. In both forms teardown must run even when there is nothing to clear (an expired-but-still-connected session is the case that most needs logout to take effect), and the credential is read only after teardown so an in-flight login that commits mid-teardown is still caught and cleared -- see CloudLoginCommand.logout(CommandSender)'s own comments for why that order matters.

      CR-01 (16-REVIEW-cloud.md): this method captures CloudSession.current() exactly once -- in the statement below, immediately before teardown begins -- and passes that captured reference to PluginInitiationUtils.disableCloud(CloudSession), which acts on and returns that exact instance. The revoke-and-delete decision then acts on the returned reference, never on a second, independent call to CloudSession.current() taken after teardown returns. Reading current() a second time, afterward, used to be exactly the bug: a concurrent login() (unsynchronized, and reachable from a different thread via @RunAsync) can install a brand-new session with CloudSession.startNew() in the gap while teardown is running, and that second read would then see the NEW session's (always null) credential instead of the one actually being logged out of -- concluding "nothing to clear" and leaving the real credential on disk. Acting on the one reference this method already holds makes that race structurally impossible: there is no second read left to disagree with the first. This method deliberately does not call CloudSession.startNew() itself any more either; clearing the torn-down session in place is sufficient, and the next real login installs a fresh session via its own CloudSession.startNew() call, exactly as it always has.

      Round-1 external review finding: the paragraph above establishes WHICH session this method acts on, but not what its delete is safe to remove. A concurrent login() can write a fresh credential to the same shared document while this session's teardown is in flight, so the delete is a compare-and-delete keyed on the retiring credential's id (ServerCredentialStore.deleteIfMatches(String)): it never removes a credential the torn-down session did not hold. A login() that installs its new session after this method's single read of CloudSession.current() has lost the session race regardless -- CloudSession.startNew() invalidates whatever session it replaces.

      Returns:
      true if a credential existed and cleanup was started, or a pending authentication attempt was cancelled (real-machine UAT fix, plan 16-08 -- see CloudSession.hasAnythingToClear()); false if there was nothing to clear and nothing pending (teardown still ran regardless)
      Throws:
      IOException - never as of 6.3.1 (a failed local delete is reported by the asynchronous task); kept in the 6.3.0 signature
    • logoutReporting

      public static void logoutReporting(Consumer<CloudAuthManager.LogoutOutcome> onDone)
      Stops the captured session synchronously, then revokes and deletes its credential on a Bukkit asynchronous task. The callback runs on that task, or inline when no credential is held. A local storage failure is propagated or reported by the scheduler, never reported as deletion.
      Parameters:
      onDone - receives the completed outcome
      Since:
      6.3.1
    • beginLogout

      private static boolean beginLogout(Consumer<CloudAuthManager.LogoutOutcome> onDone)
    • reconnect

      public static CloudAuthManager.ReconnectResult reconnect()
      Requests an immediate connection attempt without waiting for the network. The ten-second cooldown applies to every invocation, including one refused by the current connection state.
      Returns:
      whether an attempt was queued, or why the request was refused
      Since:
      6.3.1
    • status

      public static CloudAuthManager.CloudStatus status()
      Returns:
      an immutable snapshot of the current session's connection state, for /ulticloud status to render -- never the token itself (D-18)