Lightning.Credentials.OauthToken (Lightning v2.13.4)

View Source

Schema and functions for managing OAuth tokens. This module handles the storage and validation of OAuth tokens in a 1:1 relationship with credentials.

Each token contains all required OAuth 2.0 fields:

  • access_token (required)
  • refresh_token (required)
  • token_type (required, must be "Bearer")
  • scope/scopes (required)
  • expires_in/expires_at (required)

Integrates with the enhanced OauthValidation module for comprehensive validation.

Summary

Functions

Creates a changeset for validating and creating an OAuth token.

Checks if the token is still fresh (not expired or about to expire).

Creates a changeset for updating token data.

Types

t()

@type t() :: %Lightning.Credentials.OauthToken{
  __meta__: term(),
  body: map(),
  credential: Lightning.Credentials.Credential.t() | nil,
  id: Ecto.UUID.t() | nil,
  inserted_at: DateTime.t() | nil,
  last_refreshed: DateTime.t() | nil,
  oauth_client: Lightning.Credentials.OauthClient.t() | nil,
  oauth_client_id: Ecto.UUID.t() | nil,
  oauth_error_details: term(),
  oauth_error_type: term(),
  scopes: [String.t()],
  updated_at: DateTime.t() | nil,
  user: Lightning.Accounts.User.t() | nil,
  user_id: Ecto.UUID.t() | nil
}

Functions

changeset(attrs)

Creates a changeset for validating and creating an OAuth token.

Parameters

  • attrs - A map containing token attributes:
    • :body - The token data (required) - must include access_token, refresh_token, token_type, scope/scopes, expires_in/expires_at
    • :scopes - List of permission scopes for the token (optional, will be extracted from body if not provided)
    • :oauth_client_id - Reference to the OAuth client (required)
    • :user_id - Reference to the user (required)

Validations

  • All required fields are validated
  • Referenced oauth_client and user must exist
  • Token body must contain valid OAuth 2.0 fields
  • Scopes are automatically extracted and normalized if not provided

Examples

iex> OauthToken.changeset(%{
...>   body: %{
...>     "access_token" => "abc123",
...>     "refresh_token" => "xyz789",
...>     "token_type" => "Bearer",
...>     "scope" => "read write",
...>     "expires_in" => 3600
...>   },
...>   oauth_client_id: client.id,
...>   user_id: user.id
...> })
#Ecto.Changeset<...>

changeset(oauth_token, attrs)

@spec changeset(t() | map(), map()) :: Ecto.Changeset.t()

still_fresh?(oauth_token, buffer_minutes \\ 5)

@spec still_fresh?(t(), non_neg_integer()) :: boolean()

Checks if the token is still fresh (not expired or about to expire).

Parameters

  • oauth_token - The token to check
  • buffer_minutes - Minutes before expiry to consider stale (default: 5)

Returns

  • true - Token is fresh and can be used
  • false - Token is stale (expired or expires within buffer period)

Examples

iex> OauthToken.still_fresh?(token)
true

iex> OauthToken.still_fresh?(token, 10)  # More conservative buffer
false

update_changeset(oauth_token, attrs)

@spec update_changeset(t(), map()) :: Ecto.Changeset.t()

Creates a changeset for updating token data.

Preserves the refresh_token from the existing token if the provider doesn't return a new one. This is common with some OAuth providers that only return refresh_token on initial authorization.

By default, this function updates the last_refreshed timestamp to the current UTC time. If the update is not the result of an actual token refresh (e.g., only scopes changed), you can pass the option refreshed: false to skip updating the timestamp.

Parameters

  • oauth_token - The existing OauthToken struct.
  • new_token - A map containing new token data from the OAuth provider.
  • opts - (optional) A keyword list of options:
    • :refreshed - Whether this update corresponds to an actual token refresh (default: true).

Examples

iex> OauthToken.update_token_changeset(existing_token, %{
...>   "access_token" => "new_access_token",
...>   "expires_in" => 3600,
...>   "scope" => "read write admin"
...> })
#Ecto.Changeset<...>

iex> OauthToken.update_token_changeset(existing_token, new_token_map, refreshed: false)
#Ecto.Changeset<...>