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
Returns an %Ecto.Changeset{}
for tracking credential changes.
Examples
iex> change_credential(credential)
%Ecto.Changeset{data: %Credential{}}
@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 thereceiver
. - 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 theCredential
being transferred.receiver_id
: The ID of theUser
receiving the credential.owner_id
: The ID of theUser
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
@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 IDschema
- Schema type ("oauth" for OAuth credentials)oauth_client_id
- OAuth client ID (for OAuth credentials)body
- Credential configuration dataoauth_token
- OAuth token data (for OAuth credentials)
Returns
{:ok, credential}
- Successfully created credential{:error, error}
- Error with creation process
@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<...>
@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<...>
Deletes a credential.
Examples
iex> delete_credential(credential)
{:ok, %Credential{}}
iex> delete_credential(credential)
{:error, %Ecto.Changeset{}}
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:
- Mandatory scopes (required by the OAuth client but not unique to specific services)
- 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)
@spec get_schema(String.t()) :: Lightning.Credentials.Schema.t()
Creates a credential schema from credential json schema.
Checks if a given Credential
has any associated Step
activity.
Parameters
_credential
: ACredential
struct. Only theid
field is used by the function.
Returns
true
if there's at least oneStep
associated with the givenCredential
.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.
@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
: TheUser
who currently owns the credential.receiver
: TheUser
who will receive the credential.credential
: TheCredential
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
@spec list_credentials(Lightning.Projects.Project.t()) :: [ Lightning.Credentials.Credential.t() ]
@spec list_credentials(Lightning.Accounts.User.t()) :: [ Lightning.Credentials.Credential.t() ]
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}]
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:
- Non-OAuth credentials: Returns the credential unchanged
- OAuth credential with missing token: Returns the credential unchanged
- OAuth credential with missing client: Returns the credential unchanged
- OAuth credential with fresh token: Returns the credential unchanged
- OAuth credential with expired token: Attempts to refresh the token
Parameters
credential
: TheCredential
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 stringsvalue
: 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
Normalizes OAuth scopes from various input formats into a consistent list.
This function handles different inputs:
nil
: Returns an empty listOauthToken
: 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 nildelimiter
: 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.
@spec revoke_transfer(String.t(), Lightning.Accounts.User.t()) :: {:ok, Lightning.Credentials.Credential.t()} | {:error, transfer_error() | Ecto.Changeset.t()}
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 theCredential
being revoked.owner
: TheUser
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
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
: ACredential
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{}}
@spec sensitive_values_for(Ecto.UUID.t() | Lightning.Credentials.Credential.t() | nil) :: [any()]
@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 withexpires_at
orexpires_in
fields. When usingexpires_in
, the function will use the token'supdated_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.
@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 updateattrs
- Map of attributes to update
Returns
{:ok, credential}
- Successfully updated credential{:error, error}
- Error with update process
@spec validate_credential_transfer( Ecto.Changeset.t(), Lightning.Accounts.User.t(), Lightning.Credentials.Credential.t() ) :: Ecto.Changeset.t()
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
: TheEcto.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.
validate_oauth_token_data(token_data, user_id, oauth_client_id, scopes, is_update \\ false)
View SourceValidates 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 validateuser_id
- User ID associated with the tokenoauth_client_id
- OAuth client IDscopes
- List of scopes for the tokenis_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