View Source Lightning.Credentials (Lightning v2.11.1)

The Credentials context.

Summary

Functions

Returns an %Ecto.Changeset{} for tracking credential changes.

Confirms and executes a credential transfer.

Creates a new credential.

Returns an empty changeset structure for credential transfers with an email field.

Creates a changeset for transferring credentials, validating the provided email.

Deletes a credential.

Finds the most appropriate OAuth token for a user that matches the requested scopes.

Gets a single credential.

Creates a credential schema from credential json schema.

Checks if a given Credential has any associated Step activity.

Initiates a credential transfer from the owner to the receiver.

Retrieves all credentials based on the given context, either a Project or a User.

Refreshes an OAuth token if it has expired, or returns the credential unchanged.

Normalizes all map keys to strings recursively.

Normalizes OAuth scopes with a default space delimiter.

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

Perform, when called with %{"type" => "purge_deleted"} will find credentials that are ready for permanent deletion, set their bodies to null, and try to purge them.

Revokes a pending credential transfer.

Schedules a given credential for deletion.

Determines if the token data is still considered fresh.

Updates an existing credential.

Validates a credential transfer request.

Validates OAuth token data according to OAuth standards. This function is used by both OauthToken and Credential modules to ensure consistent validation of OAuth tokens.

Types

@type transfer_error() :: :token_error | :not_found | :not_owner

Functions

Link to this function

basic_auth_for(credential)

View Source
Link to this function

cancel_scheduled_deletion(credential_id)

View Source
Link to this function

change_credential(credential, attrs \\ %{})

View Source

Returns an %Ecto.Changeset{} for tracking credential changes.

Examples

iex> change_credential(credential)
%Ecto.Changeset{data: %Credential{}}
Link to this function

confirm_transfer(credential_id, receiver_id, owner_id, token)

View Source
@spec confirm_transfer(String.t(), String.t(), String.t(), String.t()) ::
  {:ok, Lightning.Credentials.Credential.t()}
  | {:error, transfer_error() | Ecto.Changeset.t()}

Confirms and executes a credential transfer.

This function:

  • Verifies the transfer token to ensure the request is valid.
  • Transfers the credential from the owner to the receiver.
  • Records the transfer in the audit log.
  • Deletes all related credential transfer tokens.
  • Notifies both parties about the transfer.

Parameters

  • credential_id: The ID of the Credential being transferred.
  • receiver_id: The ID of the User receiving the credential.
  • owner_id: The ID of the User currently owning the credential.
  • token: The transfer token for verification.

Returns

  • {:ok, credential} on successful transfer.
  • {:error, reason} if the transfer fails.

Errors

  • {:error, :not_found} if the credential or receiver does not exist.
  • {:error, :token_error} if the token is invalid.
  • {:error, :not_owner} if the token does not match the credential owner.
  • {:error, changeset} if there is a validation or update issue.

Example

case confirm_transfer(credential_id, receiver_id, owner_id, token) do
  {:ok, credential} -> IO.puts("Transfer successful")
  {:error, :not_found} -> IO.puts("Error: Credential or receiver not found")
  {:error, :token_error} -> IO.puts("Error: Invalid transfer token")
  {:error, reason} -> IO.inspect(reason, label: "Transfer failed")
end
Link to this function

create_credential(attrs \\ %{})

View Source
@spec create_credential(map()) ::
  {:ok, Lightning.Credentials.Credential.t()} | {:error, any()}

Creates a new credential.

For regular credentials, this simply inserts the changeset. For OAuth credentials, this uses the oauth_token data provided directly, finds or creates an OAuth token, and associates it with the credential.

Parameters

  • attrs - Map of attributes for the credential including:
    • user_id - User ID
    • schema - Schema type ("oauth" for OAuth credentials)
    • oauth_client_id - OAuth client ID (for OAuth credentials)
    • body - Credential configuration data
    • oauth_token - OAuth token data (for OAuth credentials)

Returns

  • {:ok, credential} - Successfully created credential
  • {:error, error} - Error with creation process
Link to this function

credential_transfer_changeset()

View Source
@spec credential_transfer_changeset() :: Ecto.Changeset.t()

Returns an empty changeset structure for credential transfers with an email field.

Returns

An Ecto.Changeset struct with an empty data map and an email field.

Example

iex> credential_transfer_changeset()
#Ecto.Changeset<...>
Link to this function

credential_transfer_changeset(email)

View Source
@spec credential_transfer_changeset(String.t()) :: Ecto.Changeset.t()

Creates a changeset for transferring credentials, validating the provided email.

Parameters

  • email: The email address to be included in the changeset.

Returns

An Ecto.Changeset containing the email field.

Example

iex> credential_transfer_changeset("user@example.com")
#Ecto.Changeset<...>
Link to this function

delete_credential(credential)

View Source

Deletes a credential.

Examples

iex> delete_credential(credential)
{:ok, %Credential{}}

iex> delete_credential(credential)
{:error, %Ecto.Changeset{}}
Link to this function

find_best_matching_token_for_scopes(user_id, oauth_client_id, requested_scopes)

View Source
@spec find_best_matching_token_for_scopes(
  user_id :: Ecto.UUID.t(),
  oauth_client_id :: Ecto.UUID.t() | nil,
  requested_scopes :: [String.t()]
) :: Lightning.Credentials.OauthToken.t() | nil

Finds the most appropriate OAuth token for a user that matches the requested scopes.

This function implements a sophisticated token matching algorithm that considers:

  1. Mandatory scopes (required by the OAuth client but not unique to specific services)
  2. Service-specific scopes (like drive, calendar, mail, etc.)

## Matching Algorithm

The function first fetches all OAuth tokens that belong to the user and match the client's credentials. Then it selects the best matching token based on these rules:

For requests with only mandatory scopes:

  • Returns the token with the fewest additional non-mandatory scopes
  • If tied, selects the most recently updated token

For requests with service-specific scopes:

  • Filters to tokens with at least one matching service-specific scope
  • Ranks by: exact match, most matching scopes, fewest extra scopes, most recent update

## Complexity Analysis

Time Complexity:

  • Best case (when oauth_client_id is nil): O(1)
  • Worst case: O(log N + k × s) where:
    • N = database table size
    • k = number of tokens for the user with matching client credentials
    • s = number of scopes being evaluated

Space Complexity:

  • O(k + s) for storing retrieved tokens and scope sets

In practice, performance is excellent as k and s are typically small values.

## Parameters

- `user_id`: The ID of the user who owns the tokens
- `oauth_client_id`: The ID of the OAuth client to match tokens for
- `requested_scopes`: List of OAuth scopes being requested

## Returns

- The best matching `OauthToken` struct
- `nil` if:
  - `oauth_client_id` is nil
  - No client exists with the given ID
  - No tokens match the requested criteria

Gets a single credential.

Raises Ecto.NoResultsError if the Credential does not exist.

Examples

iex> get_credential!(123)
%Credential{}

iex> get_credential!(456)
** (Ecto.NoResultsError)
Link to this function

get_credential_by_project_credential(project_credential_id)

View Source
Link to this function

get_credential_for_update!(id)

View Source
@spec get_schema(String.t()) :: Lightning.Credentials.Schema.t()

Creates a credential schema from credential json schema.

Link to this function

has_activity_in_projects?(credential)

View Source

Checks if a given Credential has any associated Step activity.

Parameters

  • _credential: A Credential struct. Only the id field is used by the function.

Returns

  • true if there's at least one Step associated with the given Credential.
  • false otherwise.

Examples

iex> has_activity_in_projects?(%Credential{id: some_id})
true

iex> has_activity_in_projects?(%Credential{id: another_id})
false

Notes

This function leverages the association between Step and Credential to determine if any steps exist for a given credential. It's a fast check that does not load any records into memory, but simply checks for their existence.

Link to this function

initiate_credential_transfer(owner, receiver, credential)

View Source
@spec initiate_credential_transfer(
  Lightning.Accounts.User.t(),
  Lightning.Accounts.User.t(),
  Lightning.Credentials.Credential.t()
) :: :ok | {:error, transfer_error() | Ecto.Changeset.t()}

Initiates a credential transfer from the owner to the receiver.

This function:

  • Marks the credential as pending for transfer.
  • Generates an email token for the credential transfer.
  • Sends a transfer confirmation email to the receiver.

Parameters

  • owner: The User who currently owns the credential.
  • receiver: The User who will receive the credential.
  • credential: The Credential to be transferred.

Returns

  • :ok if the transfer process is successfully initiated.
  • {:error, reason} if any validation or transaction step fails.

Example

case initiate_credential_transfer(owner, receiver, credential) do
  :ok -> IO.puts("Transfer initiated successfully")
  {:error, error} -> IO.inspect(error, label: "Transfer failed")
end
Link to this function

list_credentials(project)

View Source

Retrieves all credentials based on the given context, either a Project or a User.

Parameters

  • context: The Project or User struct to retrieve credentials for.

Returns

  • A list of credentials associated with the given Project or created by the given User.

Examples

When given a Project:

iex> list_credentials(%Project{id: 1})
[%Credential{project_id: 1}, %Credential{project_id: 1}]

When given a User:

iex> list_credentials(%User{id: 123})
[%Credential{user_id: 123}, %Credential{user_id: 123}]
Link to this function

maybe_refresh_token(credential)

View Source

Refreshes an OAuth token if it has expired, or returns the credential unchanged.

This function checks the freshness of a credential's associated OAuth token and refreshes it if necessary. For non-OAuth credentials, it simply returns the credential unchanged.

Refresh Logic

The function handles several cases:

  1. Non-OAuth credentials: Returns the credential unchanged
  2. OAuth credential with missing token: Returns the credential unchanged
  3. OAuth credential with missing client: Returns the credential unchanged
  4. OAuth credential with fresh token: Returns the credential unchanged
  5. OAuth credential with expired token: Attempts to refresh the token

Parameters

  • credential: The Credential struct to check and potentially refresh

Returns

  • {:ok, credential}: If the credential was fresh or successfully refreshed
  • {:error, error}: If refreshing the token failed

Examples

iex> maybe_refresh_token(non_oauth_credential)

iex> maybe_refresh_token(fresh_oauth_credential)

iex> maybe_refresh_token(expired_oauth_credential)

Normalizes all map keys to strings recursively.

This function walks through a map and converts all keys to strings using to_string/1. If a key's value is also a map, it recursively normalizes the nested map as well. Non-map values are returned unchanged.

Examples

iex> normalize_keys(%{foo: "bar", baz: %{qux: 123}})
%{"foo" => "bar", "baz" => %{"qux" => 123}}

iex> normalize_keys(%{1 => "one", 2 => "two"})
%{"1" => "one", "2" => "two"}

iex> normalize_keys("not a map")
"not a map"

Parameters

  • map: The map whose keys should be normalized to strings
  • value: Any non-map value that should be returned as-is

Returns

  • A new map with all keys converted to strings (for map inputs)
  • The original value unchanged (for non-map inputs)

Normalizes OAuth scopes with a default space delimiter.

Convenience function that calls normalize_scopes/2 with a space delimiter.

Parameters

  • input: OAuth scope string or token to extract scopes from

Returns

  • List of normalized scope strings
Link to this function

normalize_scopes(scopes, delimiter)

View Source

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

This function handles different inputs:

  • nil: Returns an empty list
  • OauthToken: Extracts the scope from the token's body
  • String: Splits by the specified delimiter, downcases, and removes empty entries

Parameters

  • scopes: OAuth scope string, token, or nil
  • delimiter: Character or string to split the scope string by

Returns

  • List of normalized scope strings

Perform, when called with %{"type" => "purge_deleted"} will find credentials that are ready for permanent deletion, set their bodies to null, and try to purge them.

Link to this function

revoke_transfer(credential_id, owner)

View Source

Revokes a pending credential transfer.

This function:

  • Ensures the credential exists.
  • Checks that the owner is the one who initiated the transfer.
  • Confirms that the credential is still in a pending state.
  • Resets the transfer status and deletes related credential transfer tokens.

Parameters

  • credential_id: The ID of the Credential being revoked.
  • owner: The User who owns the credential and is revoking the transfer.

Returns

  • {:ok, credential} if the transfer is successfully revoked.
  • {:error, :not_found} if the credential does not exist.
  • {:error, :not_owner} if the user does not own the credential.
  • {:error, :not_pending} if the transfer is not in a pending state.
  • {:error, changeset} if there is a validation or update issue.

Example

case revoke_transfer(credential_id, owner) do
  {:ok, credential} -> IO.puts("Transfer revoked for credential")
  {:error, :not_found} -> IO.puts("Error: Credential not found")
  {:error, :not_owner} -> IO.puts("Error: You do not own this credential")
  {:error, :not_pending} -> IO.puts("Error: Transfer is not pending")
  {:error, reason} -> IO.inspect(reason, label: "Revoke failed")
end
Link to this function

schedule_credential_deletion(credential)

View Source

Schedules a given credential for deletion.

The deletion date is determined based on the :purge_deleted_after_days configuration in the application environment. If this configuration is absent, the credential is scheduled for immediate deletion.

The function will also perform necessary side effects such as:

  • Removing associations of the credential.
  • Notifying the owner of the credential about the scheduled deletion.

Parameters

  • credential: A Credential struct that is to be scheduled for deletion.

Returns

  • {:ok, credential}: Returns an :ok tuple with the updated credential struct if the update was successful.
  • {:error, changeset}: Returns an :error tuple with the changeset if the update failed.

Examples

iex> schedule_credential_deletion(%Credential{id: some_id})
{:ok, %Credential{}}

iex> schedule_credential_deletion(%Credential{})
{:error, %Ecto.Changeset{}}
Link to this function

sensitive_values_for(id)

View Source
@spec sensitive_values_for(Ecto.UUID.t() | Lightning.Credentials.Credential.t() | nil) ::
  [any()]
Link to this function

still_fresh(token, threshold \\ 5, time_unit \\ :minute)

View Source
@spec still_fresh(Lightning.Credentials.OauthToken.t(), integer(), atom()) ::
  boolean() | {:error, String.t()}

Determines if the token data is still considered fresh.

Parameters

  • token: a map containing token data with expires_at or expires_in fields. When using expires_in, the function will use the token's updated_at timestamp.
  • threshold: the number of time units before expiration to consider the token still fresh.
  • time_unit: the unit of time to consider for the threshold comparison.

Returns

  • true if the token is fresh.
  • false if the token is not fresh.
  • {:error, reason} if the token's expiration data is missing or invalid.
Link to this function

update_credential(credential, attrs)

View Source
@spec update_credential(Lightning.Credentials.Credential.t(), map()) ::
  {:ok, Lightning.Credentials.Credential.t()} | {:error, any()}

Updates an existing credential.

For regular credentials, this simply updates the changeset. For OAuth credentials, this updates the associated OAuth token with token data provided in the oauth_token parameter.

Parameters

  • credential - The credential to update
  • attrs - Map of attributes to update

Returns

  • {:ok, credential} - Successfully updated credential
  • {:error, error} - Error with update process
Link to this function

validate_credential_transfer(changeset, sender, credential)

View Source

Validates a credential transfer request.

This function ensures:

  • The email format is correct.
  • The email does not already exist in the system.
  • The credential is not transferred to the same user.
  • The recipient has access to the necessary projects.

If the changeset is valid, additional validation checks are applied.

Parameters

  • changeset: The Ecto.Changeset containing the credential transfer details.
  • current_user: The user attempting the credential transfer.
  • credential: The credential being transferred.

Returns

  • An updated Ecto.Changeset with validation errors if any issues are found.
Link to this function

validate_oauth_token_data(token_data, user_id, oauth_client_id, scopes, is_update \\ false)

View Source

Validates OAuth token data according to OAuth standards. This function is used by both OauthToken and Credential modules to ensure consistent validation of OAuth tokens.

Parameters

  • token_data - The OAuth token data to validate
  • user_id - User ID associated with the token
  • oauth_client_id - OAuth client ID
  • scopes - List of scopes for the token
  • is_update - Whether this is an update to an existing token

Returns

  • {:ok, token_data} - Token data is valid
  • {:error, reason} - Token data is invalid with reason