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 CloudSession.commit(TokenEntity) cannot be reintroduced unnoticed.

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.

  • Constructor Details

    • CloudAuthManager

      private CloudAuthManager()
  • Method Details

    • login

      public static void login(Runnable onAlreadyLoggedIn, Consumer<Long> onRateLimited, Runnable onRequesting, Consumer<String> onSuccess, Consumer<String> onError)
      Attempts a magic-link login on a fresh session, covering the already-logged-in check, the rate-limit check, the magic-link request and starting the poll -- exactly the sequence /ulticloud login performed before this plan, just no longer expressed through separate public statics on this class. Every branch is reported back through the callback that names it, so CloudLoginCommand.login(CommandSender) prints the exact same messages it always has; only where the decision is made moved.
      Parameters:
      onAlreadyLoggedIn - invoked, with nothing else attempted, if a valid token already exists
      onRateLimited - invoked with the remaining cooldown in seconds if the login attempt is currently rate-limited
      onRequesting - invoked right before the magic-link HTTP request is made
      onSuccess - invoked with the magic-link URL once the request succeeds
      onError - invoked with an error message if the magic-link request fails
    • logout

      public static boolean logout() throws IOException
      Tears down cloud features unconditionally, then clears the persisted credential if one existed. Mirrors /ulticloud logout's pre-16-09 sequence exactly: 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 disk-clear 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) token 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, corrected in the same plan: the paragraph above establishes WHICH session's disk-clear decision this method acts on, but not what that clear is safe to remove. CloudSession.clearPersisted() does NOT wipe disk unconditionally -- it delegates to TokenStore.clearIfMatches(TokenEntity), a compare-and-delete keyed on the torn-down session's own last-known token. This matters because a concurrent login() can still WRITE a fresh credential to the same shared document while this session's teardown is in flight, even though its own session-invalidation race is already closed by the paragraph above -- see CloudSession.clearPersisted()'s own javadoc for the full account of why an unconditional clear would remove that fresh write.

      Documented semantics for logout racing a concurrent login (CR-01's second half): this method's teardown always acts on whichever session was current at the single instant this method's own CloudSession.current() call below reads it -- a single, fixed point in time this method controls directly, rather than one buried inside a callee. A login() that installs its new session before that read wins outright: this logout() call never sees or touches it. A login() that installs its new session after that read has already lost the SESSION-invalidation race regardless of what this method does -- CloudSession.startNew() itself unconditionally invalidates whatever session it replaces, so the fresh login's session object is torn down by that call alone, independent of this command. What CloudSession.clearPersisted()'s compare-and-delete adds on top is the disk-level half of that same guarantee: even if the fresh login's own commit reaches the shared document before this method's clear does, the clear cannot remove a value the torn-down session never itself wrote.

      Returns:
      true if a credential existed and was cleared, 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 - if clearing the persisted credential fails
    • 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)