Lightning.Credentials.OauthValidation (Lightning v2.13.5)

View Source

Centralized OAuth token validation with structured error handling.

This module provides comprehensive OAuth token validation used across OauthToken and Credential modules to ensure consistent validation logic.

Validates OAuth 2.0 tokens according to RFC 6749 specifications with additional robustness for real-world OAuth provider variations.

Summary

Functions

Extracts scopes from OAuth token data in various formats.

Normalizes OAuth scopes from various input formats into a consistent list.

Validates that granted scopes match exactly what the user selected.

Validates OAuth token data according to OAuth 2.0 standards (RFC 6749).

Functions

extract_scopes(arg1)

@spec extract_scopes(map()) :: {:ok, [String.t()]} | :error

Extracts scopes from OAuth token data in various formats.

Handles both string and atom keys, and both "scope" and "scopes" fields.

Parameters

  • token_data - Map containing OAuth token data

Returns

  • {:ok, [String.t()]} - List of extracted scopes
  • :error - No valid scope data found

Examples

iex> extract_scopes(%{"scope" => "read write"})
{:ok, ["read", "write"]}

iex> extract_scopes(%{scopes: ["read", "write"]})
{:ok, ["read", "write"]}

iex> extract_scopes(%{"invalid" => "data"})
:error

normalize_scopes(input, delimiter \\ " ")

@spec normalize_scopes(any(), String.t()) :: [String.t()]

Normalizes OAuth scopes from various input formats into a consistent list.

Handles multiple input formats:

  • nil -> []
  • OauthToken struct with body containing "scope" or "scopes"
  • String with delimited scopes
  • List of scopes (strings or atoms)
  • Single atom scope
  • Any other input -> []

Returns lowercase, trimmed, non-empty scope strings with duplicates removed.

Parameters

  • input - The input to normalize (various formats supported)
  • delimiter - String delimiter for parsing scope strings (default: " ")

Returns

  • [String.t()] - List of normalized scope strings

Examples

iex> normalize_scopes("READ Write ADMIN")
["read", "write", "admin"]

iex> normalize_scopes([:read, "WRITE", " admin "])
["read", "write", "admin"]

iex> normalize_scopes(%OauthToken{body: %{"scope" => "read write"}})
["read", "write"]

validate_scope_grant(token_data, expected_scopes)

@spec validate_scope_grant(map(), [String.t()]) ::
  :ok | {:error, Lightning.Credentials.OauthValidation.Error.t()}

Validates that granted scopes match exactly what the user selected.

Performs case-insensitive scope matching to handle OAuth provider variations.

Parameters

  • token_data - The OAuth token response from the provider
  • expected_scopes - List of scopes the user selected

Returns

  • :ok - All expected scopes were granted
  • {:error, %Error{}} - Some expected scopes missing or no scope data

Examples

iex> validate_scope_grant(%{"scope" => "read write admin"}, ["read", "write"])
:ok

iex> validate_scope_grant(%{"scope" => "read"}, ["read", "write"])
{:error, %Error{type: :missing_scopes, details: %{missing_scopes: ["write"]}}}

validate_token_data(token_data)

@spec validate_token_data(any()) ::
  {:ok, map()} | {:error, Lightning.Credentials.OauthValidation.Error.t()}

Validates OAuth token data according to OAuth 2.0 standards (RFC 6749).

This function validates that all required OAuth fields are present and valid in the token data. Required fields: access_token, refresh_token, scope (or scopes), expires_in (or expires_at), token_type.

Expiration Fields

  • expires_in: Duration in seconds until token expires (relative, preferred by OAuth 2.0)
  • expires_at: Absolute timestamp when token expires (Unix timestamp or ISO 8601)

Only one expiration field is required. expires_in is preferred as it's less susceptible to clock skew issues between client and server.

Parameters

  • token_data - The OAuth token data to validate

Returns

  • {:ok, token_data} - Token data is valid
  • {:error, %Error{}} - Token data is invalid with structured error

Examples

iex> validate_token_data(%{
...>   "access_token" => "abc123",
...>   "refresh_token" => "def456",
...>   "token_type" => "Bearer",
...>   "expires_in" => 3600,  # 1 hour
...>   "scope" => "read write"
...> })
{:ok, %{"access_token" => "abc123", ...}}

iex> validate_token_data(%{
...>   "access_token" => "abc123",
...>   "refresh_token" => "def456",
...>   "token_type" => "Bearer",
...>   "expires_at" => 1672531200,  # Unix timestamp
...>   "scope" => "read write"
...> })
{:ok, %{"access_token" => "abc123", ...}}

iex> validate_token_data(%{"invalid" => "data"})
{:error, %Error{type: :missing_access_token, message: "..."}}