Rho.Auth.Login (rho v0.1.0)

Copy Markdown View Source

Signing in to a subscription (:anthropic, :openai_codex): the OAuth flow (Rho.Auth.OAuthFlow) with the provider's own localhost redirect, answered by this BEAM.

begin/1 starts a :gen_tcp listener on 127.0.0.1 at the redirect's port (54545 for Anthropic, 1455 for OpenAI: the redirects omp registers), opens the authorize URL in the device's browser, and when the browser is sent back to http://localhost:<port>/…?code=…&state=… exchanges the code, stores the credentials (Rho.Auth.put/2, which tells its subscribers) and sends whoever called begin/1 {:rho_login, provider, :ok | {:error, message}}.

Anthropic's authorize page may show the code (code#state) instead of redirecting; paste/2 takes it (or the whole redirect URL) for the flow in progress. One flow at a time: begin/1 replaces an unfinished one, and cancels an exchange still running for an older one (its result is never used). A redirect carrying another flow's state (an old tab) gets an error page and leaves the flow in progress alone; the flow ends on its own redirect, a paste, or after 15 minutes without either.

The exchange runs in a worker and retries transport errors every 2 s for up to 9 minutes: Android blocks a backgrounded app's network (~60 s after it leaves the foreground), so it completes when the user switches back from the browser.

Options: :open_url (fn url -> :ok end; default Rho.Config.open_url/1, which a mob app points at Mob.Device.open_url/1), :redirect (fn provider -> uri end, default the provider's), :respond (tests: Rho.Auth.OAuthFlow), :flow_timeout_ms.

Summary

Functions

Starts a sign-in and opens the browser; returns the URL that was opened. {:error, :not_oauth} for a provider that takes only an API key.

Returns a specification to start this module under a supervisor.

The host default for config :rho, :open_url: the computer's browser (open on macOS, xdg-open elsewhere on Unix, start on Windows), started without waiting for it. When there's none it logs that and still returns :ok: begin/1 returns the URL for the host to show.

Finishes the flow in progress with what the provider's page showed: a code#state, a bare code, or the redirect URL.

The provider of the sign-in in progress (nil when none), whose result goes to the caller from now on: a page reopened during the browser flow takes it over from the one that started it.

Started by Rho.Supervisor (its :login option holds opts, see the moduledoc).

Functions

begin(provider)

@spec begin(Rho.Auth.provider()) :: {:ok, String.t()} | {:error, term()}

Starts a sign-in and opens the browser; returns the URL that was opened. {:error, :not_oauth} for a provider that takes only an API key.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

open_in_browser(url)

@spec open_in_browser(String.t()) :: :ok

The host default for config :rho, :open_url: the computer's browser (open on macOS, xdg-open elsewhere on Unix, start on Windows), started without waiting for it. When there's none it logs that and still returns :ok: begin/1 returns the URL for the host to show.

paste(provider, input)

@spec paste(Rho.Auth.provider(), String.t()) ::
  :ok
  | {:error,
     :no_login_started | :no_code | :state_mismatch | {:started_for, atom()}}

Finishes the flow in progress with what the provider's page showed: a code#state, a bare code, or the redirect URL.

pending()

@spec pending() :: Rho.Auth.provider() | nil

The provider of the sign-in in progress (nil when none), whose result goes to the caller from now on: a page reopened during the browser flow takes it over from the one that started it.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Started by Rho.Supervisor (its :login option holds opts, see the moduledoc).