Tango (tango v0.2.0)

Copy Markdown View Source

OAuth2 integration library for Elixir applications.

Provides OAuth2 Authorization Code Flow with PKCE support, multi-tenant token management, and provider configuration.

Ready-to-Use API

For Phoenix applications, Tango provides a complete OAuth API that can be mounted with a single line:

# In your Phoenix router
scope "/api/oauth" do
  pipe_through :api
  forward "/", Tango.API.Router
end

See Tango.API for complete setup and usage documentation.

Direct Library Usage

You can also use Tango's functions directly in your application code:

Summary

Functions

Generates OAuth authorization URL with PKCE support.

Deletes expired and consumed authorization candidates.

Cleans up expired OAuth sessions.

Creates an authorization session with server-bound intent and completion data.

Creates a new provider.

Creates provider from Nango configuration.

Creates OAuth session for provider and tenant.

Soft deletes a provider.

Exchanges authorization code for access token.

Gets an active connection by ID with tenant isolation and optional refresh.

Gets connection for provider and tenant.

Gets provider by name.

Gets provider by ID.

Gets OAuth session by token.

Lists active connections for a tenant.

Lists active OAuth providers.

Marks connection as used (updates timestamp).

Adds atomic authorization finalization to a consumer-owned Ecto.Multi.

Records the provider rejecting a connection's credentials.

Clears a connection's rejection streak after a call authenticates again.

Refreshes an OAuth connection's access token.

Refreshes connections about to expire.

Revokes a connection for a tenant.

Exchanges an authorization code into an encrypted, short-lived candidate.

Updates a provider.

Verifies a staged authorization candidate with a trusted server callback.

Verifies and conditionally binds the identity of an active connection.

Functions

authorize_url(session_token, opts \\ [])

Generates OAuth authorization URL with PKCE support.

Examples

iex> Tango.authorize_url("session_token", redirect_uri: "https://app.com/callback")
{:ok, "https://provider.com/oauth/authorize?..."}

cleanup_expired_authorizations()

Deletes expired and consumed authorization candidates.

Returns {:ok, deleted_count}. Token columns are wiped before the rows are deleted, and the audit entry records only the count and a cleanup category.

Tango does not start a scheduler. Schedule this from the consuming application's own job runner (Oban, Quantum, a GenServer timer, ...) together with Tango.cleanup_expired_sessions/0, which is also unscheduled by default. Run this function first so candidate credentials are wiped before the sessions they belong to are deleted:

def perform(_job) do
  {:ok, _candidates} = Tango.cleanup_expired_authorizations()
  {:ok, _sessions} = Tango.cleanup_expired_sessions()
  :ok
end

Running it every few minutes keeps the candidate table small, since candidates live for at most ten minutes.

cleanup_expired_sessions()

Cleans up expired OAuth sessions.

Examples

iex> Tango.cleanup_expired_sessions()
{:ok, 5}  # 5 sessions cleaned up

create_authorization_session(provider_name, tenant_id, opts)

Creates an authorization session with server-bound intent and completion data.

create_provider(attrs)

Creates a new provider.

Examples

iex> Tango.create_provider(%{name: "custom", display_name: "Custom OAuth"})
{:ok, %Tango.Schemas.Provider{}}

create_provider_from_nango(name, nango_config, opts \\ [])

Creates provider from Nango configuration.

Examples

iex> Tango.create_provider_from_nango("github", nango_config, client_id: "abc", client_secret: "xyz")
{:ok, %Tango.Schemas.Provider{}}

create_session(provider_name, tenant_id, opts \\ [])

Creates OAuth session for provider and tenant.

Returns session that can be used to generate authorization URL.

Examples

iex> Tango.create_session("github", "user-123")
{:ok, %Tango.Schemas.OAuthSession{}}

iex> Tango.create_session("nonexistent", "user-123")
{:error, :provider_not_found}

delete_provider(provider)

Soft deletes a provider.

Examples

iex> Tango.delete_provider(provider)
{:ok, %Tango.Schemas.Provider{active: false}}

exchange_code(state, code, tenant_id, opts)

Exchanges authorization code for access token.

Validates session state, exchanges code with provider, and creates connection with multi-tenant isolation.

Examples

iex> Tango.exchange_code("state", "code", "tenant-123", redirect_uri: "https://app.com/callback")
{:ok, %Tango.Schemas.Connection{}}

get_active_connection(connection_id, tenant_id, opts \\ [])

Gets an active connection by ID with tenant isolation and optional refresh.

get_connection_for_provider(provider_name, tenant_id)

Gets connection for provider and tenant.

Examples

iex> Tango.get_connection_for_provider("github", "user-123")
{:ok, %Tango.Schemas.Connection{}}

iex> Tango.get_connection_for_provider("github", "user-123", auto_refresh: true)
{:ok, %Tango.Schemas.Connection{}}  # Automatically refreshed if needed

get_connection_for_provider(provider_name, tenant_id, opts)

See Tango.Connection.get_connection_for_provider/3.

get_provider(name)

Gets provider by name.

Examples

iex> Tango.get_provider("github")
{:ok, %Tango.Schemas.Provider{}}

get_provider_by_id(id)

Gets provider by ID.

Examples

iex> Tango.get_provider_by_id(provider_id)
{:ok, %Tango.Schemas.Provider{}}

get_session(session_token)

Gets OAuth session by token.

Examples

iex> Tango.get_session("session_token")
{:ok, %Tango.Schemas.OAuthSession{}}

list_connections(tenant_id, opts \\ [])

Lists active connections for a tenant.

Examples

iex> Tango.list_connections("user-123")
[%Tango.Schemas.Connection{}, ...]

list_providers()

Lists active OAuth providers.

Examples

iex> Tango.list_providers()
[%Tango.Schemas.Provider{}, ...]

mark_connection_used(connection)

Marks connection as used (updates timestamp).

Examples

iex> Tango.mark_connection_used(connection)
{:ok, %Tango.Schemas.Connection{}}

multi_finalize_authorization(multi, name, handle, tenant_id, opts)

Adds atomic authorization finalization to a consumer-owned Ecto.Multi.

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

Records the provider rejecting a connection's credentials.

Call this when a provider rejects the connection's credentials outright — an HTTP 401, or a 403 whose provider-specific error body confirms an invalid or revoked token rather than a policy, scope, or quota denial. Tango cannot see the calls made with the tokens it issues, so a revoked credential stays :active until a consumer reports the rejection. The connection is expired once the rejections reach opts[:max_failures] (default 3), after which get_connection_for_provider/2 returns {:error, :not_found}. Pass a threshold matched to how often the caller hits that provider — the count is a number of rejections, not a duration.

Accepts a connection, or a connection id together with opts[:tenant_id]. Only active connections accept a report.

Examples

iex> Tango.record_auth_failure(connection, "http_401")
{:ok, %Tango.Schemas.Connection{auth_failures: 1}}

iex> Tango.record_auth_failure(connection_id, "http_401", tenant_id: "user-123", max_failures: 10)
{:ok, %Tango.Schemas.Connection{auth_failures: 1}}

record_auth_success(connection, opts \\ [])

Clears a connection's rejection streak after a call authenticates again.

Pairs with record_auth_failure/3 to keep its count consecutive. Accepts a connection, or a connection id together with opts[:tenant_id].

Examples

iex> Tango.record_auth_success(connection)
:ok

refresh_connection(connection)

Refreshes an OAuth connection's access token.

Examples

iex> Tango.refresh_connection(connection)
{:ok, %Tango.Schemas.Connection{}}

refresh_expiring_connections()

Refreshes connections about to expire.

Examples

iex> Tango.refresh_expiring_connections()
{:ok, 3}  # 3 connections refreshed

revoke_connection(connection, tenant_id)

Revokes a connection for a tenant.

Examples

iex> Tango.revoke_connection(connection, "user-123")
{:ok, %Tango.Schemas.Connection{status: :revoked}}

stage_authorization(state, code, tenant_id, opts)

Exchanges an authorization code into an encrypted, short-lived candidate.

update_provider(provider, attrs)

Updates a provider.

Examples

iex> Tango.update_provider(provider, %{display_name: "New Name"})
{:ok, %Tango.Schemas.Provider{}}

verify_authorization(handle, tenant_id, verifier, opts)

Verifies a staged authorization candidate with a trusted server callback.

verify_connection_identity(connection_id, tenant_id, verifier)

Verifies and conditionally binds the identity of an active connection.