ExMCP.Authorization.OAuthTransactionStore (ex_mcp v1.0.0-rc.8)

Copy Markdown View Source

Bounded, node-local single-use state for OAuth authorization-code flows.

Transactions move atomically through pending -> code_ready -> redeemed. The store retains only SHA-256 digests of state values and authorization codes, so neither value appears in the store's retained process state or crash reports.

The built-in store is intentionally node-local because the loopback callback flow is owned by one ExMCP client process. Applications implementing a distributed browser callback should terminate that callback on the same node or provide their own end-to-end authorization provider.

Summary

Functions

Invalidates a transaction after a failed or abandoned flow.

Returns a specification to start this module under a supervisor.

Atomically marks an authorization code as redeemed with its exact redirect URI.

Registers a new authorization transaction using a digest of its random state.

Atomically validates and consumes one callback for a transaction.

Types

callback_error()

@type callback_error() ::
  :state_mismatch
  | :missing_authorization_code
  | :missing_callback_issuer
  | :missing_expected_issuer
  | :authorization_transaction_not_found
  | :authorization_transaction_replayed
  | :oauth_transaction_store_unavailable
  | {:issuer_mismatch, keyword()}

entry()

@type entry() :: %{
  state_digest: binary(),
  issuer: String.t() | nil,
  require_issuer: boolean(),
  redirect_uri: String.t(),
  status: :pending | :consumed | :redeemed | {:code_ready, binary()},
  expires_at: integer()
}

state()

@type state() :: %{
  entries: %{optional(transaction_id()) => entry()},
  max_entries: pos_integer(),
  ttl_ms: pos_integer()
}

transaction_id()

@type transaction_id() :: String.t()

Functions

abort(transaction_id, opts \\ [])

@spec abort(
  transaction_id(),
  keyword()
) :: :ok

Invalidates a transaction after a failed or abandoned flow.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

redeem_code(transaction_id, code, redirect_uri, opts \\ [])

@spec redeem_code(transaction_id(), String.t(), String.t(), keyword()) ::
  :ok
  | {:error,
     :authorization_transaction_not_found
     | :authorization_code_not_ready
     | :authorization_code_replayed
     | :authorization_code_mismatch
     | :redirect_uri_mismatch
     | :oauth_transaction_store_unavailable}

Atomically marks an authorization code as redeemed with its exact redirect URI.

register(state, issuer, redirect_uri, opts \\ [])

@spec register(String.t(), String.t() | nil, String.t(), keyword()) ::
  {:ok, transaction_id()} | {:error, term()}

Registers a new authorization transaction using a digest of its random state.

start_link(opts \\ [])

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

validate_callback(transaction_id, callback, opts \\ [])

@spec validate_callback(transaction_id(), map(), keyword()) ::
  {:ok, String.t()} | {:error, callback_error()}

Atomically validates and consumes one callback for a transaction.