Tango.Schemas.Connection (tango v0.1.2)

Copy Markdown View Source

Active OAuth connections with encrypted token storage.

Stores OAuth connections with multi-tenant isolation, token lifecycle management, and automatic refresh capabilities. All sensitive tokens are encrypted at rest.

Summary

Functions

Whether the provider has rejected these credentials enough times to expire them

Checks if connection can be refreshed

Creates a changeset for connection management

Clears a rejection streak after the credentials authenticate again.

Creates connection from OAuth token response

Extracts the raw access token string for API usage

Checks if token needs refresh

Records the provider rejecting the stored credentials.

Records a failed refresh attempt

Updates connection after successful token refresh

Types

t()

@type t() :: %Tango.Schemas.Connection{
  __meta__: term(),
  access_token: binary() | nil,
  auth_failures: non_neg_integer(),
  auto_refresh_enabled: boolean(),
  connection_config: map(),
  expires_at: DateTime.t() | nil,
  granted_scopes: [String.t()] | nil,
  id: integer() | nil,
  inserted_at: NaiveDateTime.t() | nil,
  last_auth_failure: String.t() | nil,
  last_refresh_failure: String.t() | nil,
  last_used_at: DateTime.t() | nil,
  lock_version: integer(),
  metadata: map(),
  next_refresh_at: DateTime.t() | nil,
  provider: Tango.Schemas.Provider.t() | Ecto.Association.NotLoaded.t() | nil,
  provider_id: integer() | nil,
  raw_payload: map(),
  refresh_attempts: non_neg_integer(),
  refresh_exhausted: boolean(),
  refresh_token: binary() | nil,
  status: :active | :revoked | :expired,
  tenant_id: String.t() | nil,
  token_type: :bearer | :token | nil,
  updated_at: NaiveDateTime.t() | nil
}

Functions

auth_failures_exhausted?(connection, opts \\ [])

Whether the provider has rejected these credentials enough times to expire them

can_refresh?(connection)

Checks if connection can be refreshed

changeset(connection, attrs)

Creates a changeset for connection management

clear_auth_failures(connection)

Clears a rejection streak after the credentials authenticate again.

Keeps the count consecutive: without this, unrelated rejections spread over months would accumulate to the threshold and expire a working connection.

from_token_response(provider_id, tenant_id, token_response)

Creates connection from OAuth token response

get_raw_access_token(connection)

Extracts the raw access token string for API usage

needs_refresh?(connection)

Checks if token needs refresh

record_auth_failure(connection, error_reason, opts \\ [])

Records the provider rejecting the stored credentials.

Consumers report a provider's own rejection — an HTTP 401 or 403 answering an API call — which this library cannot observe for itself. The connection is expired once the rejections reach opts[:max_failures] (default 3), after which get_connection_for_provider/2 stops handing it out and the consumer can treat the connection as gone.

The threshold is per call rather than library-wide because a Tango deployment serves many providers, each called on its own cadence — and the count is a number of rejections, not a duration. A consumer polling a provider every few seconds reaches a given ceiling within seconds of a transient outage, while one sweeping twice an hour takes days to reach the same count; each caller passes the threshold that matches how often it hits that provider.

A rejection is not a refresh failure and leaves the refresh counters alone. The two are independent: a connection carrying no refresh token and no expiry is never eligible for refresh, so its credentials can be rejected forever while refresh_attempts stays at zero.

Only an :active connection is moved to :expired; a terminal status is kept, since :revoked and :expired rows age out on different schedules. Raises ArgumentError when opts[:max_failures] is not a positive integer.

record_refresh_failure(connection, error_reason)

Records a failed refresh attempt

refresh_changeset(connection, token_response)

Updates connection after successful token refresh