Lightning.Credentials (Lightning v2.19.0-pre)

View Source

The Credentials context.

Summary

Functions

Retrieves basic auth strings for a credential in a specific environment.

Extracts basic auth strings from a credential body map.

Clears a credential's scheduled deletion, putting it back in normal use.

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

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

Confirms and executes a credential transfer.

Creates a new credential with its credential bodies.

Creates a keychain credential on behalf of an actor.

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.

Deletes a keychain credential on behalf of an actor.

Gets a single credential.

Gets a credential body for a specific environment.

Gets a single keychain 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.

Returns the list of keychain credentials for a project.

Returns all credentials owned by a specific user that are also being used in a specific project.

Creates a new keychain credential struct with proper associations.

Normalizes OAuth token expiry time to always use expires_at.

Checks if an OAuth token body is expired or expires soon.

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.

Gets a credential body for an environment and refreshes OAuth tokens if expired.

Revokes a pending credential transfer.

Schedules a given credential for deletion.

Retrieves sensitive values for a credential in a specific environment.

Extracts sensitive values from a credential body map.

Updates an existing credential and its credential bodies.

Updates a keychain credential on behalf of an actor.

Validates a credential transfer request.

Types

oauth_refresh_error()

@type oauth_refresh_error() :: :temporary_failure | :reauthorization_required

transfer_error()

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

Functions

basic_auth_for(credential, environment \\ "main")

@spec basic_auth_for(Lightning.Credentials.Credential.t() | nil, String.t()) :: [
  String.t()
]

Retrieves basic auth strings for a credential in a specific environment.

Used primarily for scrubbing historical dataclips where we need to look up the credential body by environment.

Parameters

  • credential: Credential struct or nil
  • environment: Environment name (defaults to "main")

Examples

iex> basic_auth_for(credential, "staging")
["dXNlcjpwYXNz"]

basic_auth_from_body(body)

@spec basic_auth_from_body(map() | nil) :: [String.t()]

Extracts basic auth strings from a credential body map.

Examples

iex> basic_auth_from_body(%{"username" => "user", "password" => "pass"})
["dXNlcjpwYXNz"]

cancel_scheduled_deletion(credential_id, actor)

@spec cancel_scheduled_deletion(Ecto.UUID.t(), Lightning.Accounts.User.t()) ::
  {:ok, Lightning.Credentials.Credential.t()}
  | {:error, :unauthorized | Ecto.Changeset.t()}

Clears a credential's scheduled deletion, putting it back in normal use.

Takes an id rather than a struct because the screen that calls it has only the id to hand. It loads the credential and asks the same ownership question as every other write here, so the id being caller-supplied does not matter.

change_credential(credential, attrs \\ %{})

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

Examples

iex> change_credential(credential)
%Ecto.Changeset{data: %Credential{}}

change_keychain_credential(keychain_credential, attrs \\ %{})

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

Examples

iex> change_keychain_credential(keychain_credential)
%Ecto.Changeset{data: %KeychainCredential{}}

confirm_transfer(token, confirming_user)

@spec confirm_transfer(String.t(), Lightning.Accounts.User.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.
  • Notifies both parties about the transfer.

The credential and receiver are derived from the verified token, never from caller input, so a confirmation link cannot be re-pointed at a different credential or receiver.

Parameters

  • token: The signed transfer token minted at initiation.
  • confirming_user: The User acting on the confirmation link.

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, expired or revoked.
  • {:error, :not_owner} if the confirming user isn't the token owner, or the token owner doesn't own the credential.
  • {:error, :not_pending} if the transfer isn't in a pending state.
  • {:error, changeset} if there is a validation or update issue.

Example

case confirm_transfer(token, current_user) 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

create_credential(attrs, actor)

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

Creates a new credential with its credential bodies.

Parameters

  • attrs - Map of attributes for the credential including:
    • user_id - User ID (required)
    • name - Credential name (required)
    • schema - Schema type (e.g., "oauth", "raw", etc.)
    • oauth_client_id - OAuth client ID (for OAuth credentials)
    • credential_bodies - List of credential body maps, each containing:
      • name - Environment name (e.g., "production", "staging")
      • body - Credential configuration data
    • expected_scopes - List of expected scopes (for OAuth credentials)

Returns

  • {:ok, credential} - Successfully created credential
  • {:error, error} - Error with creation process

create_keychain_credential(keychain_credential, attrs, actor)

@spec create_keychain_credential(
  Lightning.Credentials.KeychainCredential.t(),
  map(),
  Lightning.Accounts.User.t()
) ::
  {:ok, Lightning.Credentials.KeychainCredential.t()}
  | {:error, Ecto.Changeset.t() | :unauthorized}

Creates a keychain credential on behalf of an actor.

The actor is an argument rather than something the caller is trusted to have checked, so a screen cannot create a keychain by forgetting to ask. Returns {:error, :unauthorized} when the actor may not create one here.

Examples

iex> create_keychain_credential(keychain, %{name: "My Keychain"}, user)
{:ok, %KeychainCredential{}}

iex> create_keychain_credential(keychain, %{name: nil}, user)
{:error, %Ecto.Changeset{}}

credential_transfer_changeset()

@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<...>

credential_transfer_changeset(email)

@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<...>

delete_credential(credential, actor)

Deletes a credential.

Examples

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

iex> delete_credential(credential, actor)
{:error, :unauthorized}

delete_keychain_credential(keychain_credential, actor)

@spec delete_keychain_credential(
  Lightning.Credentials.KeychainCredential.t(),
  Lightning.Accounts.User.t()
) ::
  {:ok, Lightning.Credentials.KeychainCredential.t()}
  | {:error, Ecto.Changeset.t() | :unauthorized}

Deletes a keychain credential on behalf of an actor.

Returns {:error, :unauthorized} when the actor may not delete it.

Examples

iex> delete_keychain_credential(keychain_credential, user)
{:ok, %KeychainCredential{}}

get_credential(id)

get_credential!(id)

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)

get_credential_body(credential_id, env_name)

@spec get_credential_body(String.t(), String.t()) ::
  Lightning.Credentials.CredentialBody.t() | nil

Gets a credential body for a specific environment.

Returns nil if no body exists for the given credential and environment combination.

Examples

iex> get_credential_body(credential_id, "production")
%CredentialBody{name: "production", body: %{...}}

iex> get_credential_body(credential_id, "nonexistent")
nil

get_keychain_credential(id)

Gets a single keychain credential.

Raises Ecto.NoResultsError if the KeychainCredential does not exist.

Examples

iex> get_keychain_credential(123)
%KeychainCredential{}

iex> get_keychain_credential(456)
** (Ecto.NoResultsError)

get_schema(schema_name)

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

Creates a credential schema from credential json schema.

has_activity_in_projects?(credential)

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.

initiate_credential_transfer(owner, receiver, credential)

@spec initiate_credential_transfer(
  Lightning.Accounts.User.t(),
  Lightning.Accounts.User.t(),
  Lightning.Credentials.Credential.t()
) :: :ok | {:error, :not_owner | 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

list_credentials(project)

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}]

list_keychain_credentials_for_project(project)

Returns the list of keychain credentials for a project.

Examples

iex> list_keychain_credentials_for_project(%Project{id: 123})
[%KeychainCredential{}, ...]

list_user_credentials_in_project(user, project)

@spec list_user_credentials_in_project(
  Lightning.Accounts.User.t(),
  Lightning.Projects.Project.t()
) :: [
  Lightning.Credentials.Credential.t()
]

Returns all credentials owned by a specific user that are also being used in a specific project.

Parameters

  • user: The User struct whose credentials we want to find.
  • project: The Project struct to check for credential usage.

Returns

  • A list of Credential structs that are owned by the user and used in the project.

Examples

iex> list_user_credentials_in_project(%User{id: 123}, %Project{id: 456})
[%Credential{user_id: 123, ...}, %Credential{user_id: 123, ...}]

new_keychain_credential(user, project)

Creates a new keychain credential struct with proper associations.

This function ensures that the created_by and project associations are properly set and cannot be tampered with via browser params.

Examples

iex> new_keychain_credential(user, project)
%KeychainCredential{created_by: user, project: project}

normalize_token_expiry(token)

@spec normalize_token_expiry(map()) :: map()

Normalizes OAuth token expiry time to always use expires_at.

Converts relative expires_in to absolute expires_at timestamp based on current time. If the token already has expires_at, returns it unchanged.

Examples

iex> normalize_token_expiry(%{"expires_in" => 3600})
%{"expires_in" => 3600, "expires_at" => 1234567890}

iex> normalize_token_expiry(%{"expires_at" => 1234567890})
%{"expires_at" => 1234567890}

oauth_token_expired?(body)

@spec oauth_token_expired?(map()) :: boolean()

Checks if an OAuth token body is expired or expires soon.

Returns true if the token expires within the next 5 minutes (300 seconds buffer). If expiry cannot be determined, conservatively returns true to trigger refresh.

Examples

iex> oauth_token_expired?(%{"expires_at" => future_timestamp})
false

iex> oauth_token_expired?(%{"expires_at" => soon_timestamp})
true

perform(job)

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.

resolve_credential_body(credential, environment)

@spec resolve_credential_body(Lightning.Credentials.Credential.t(), String.t()) ::
  {:ok, map()} | {:error, :environment_not_found | oauth_refresh_error()}

Gets a credential body for an environment and refreshes OAuth tokens if expired.

This is the primary function for credential resolution during workflow execution. It handles the full flow: fetch body → check expiration → refresh if needed → return final body.

Parameters

  • credential: The credential struct
  • environment: Environment name (e.g., "production", "staging")

Returns

  • {:ok, body} - The credential body (with fresh tokens if OAuth was refreshed)
  • {:error, :environment_not_found} - No body exists for this environment
  • {:error, oauth_refresh_error()} - OAuth refresh failed

revoke_transfer(credential_id, owner)

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.

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

schedule_credential_deletion(credential, actor)

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.
  • Revoking OAuth tokens if the credential is an OAuth credential.
  • Notifying the owner of the credential about the scheduled deletion.

Parameters

  • credential: A Credential struct that is to be scheduled for deletion.
  • actor: The User asking, who must own the credential.

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}, actor)
{:ok, %Credential{}}

iex> schedule_credential_deletion(%Credential{}, actor)
{:error, %Ecto.Changeset{}}

sensitive_values_for(id_or_credential, environment \\ "main")

@spec sensitive_values_for(
  Ecto.UUID.t() | Lightning.Credentials.Credential.t() | nil,
  String.t()
) :: [
  any()
]

Retrieves sensitive values for a credential in a specific environment.

Used primarily for scrubbing historical dataclips where we need to look up the credential body by environment.

Parameters

  • id_or_credential: Credential ID, Credential struct, or nil
  • environment: Environment name (defaults to "main")

Examples

iex> sensitive_values_for(credential, "production")
["secret123", "api_key_xyz"]

sensitive_values_from_body(body)

@spec sensitive_values_from_body(map() | nil) :: [any()]

Extracts sensitive values from a credential body map.

Examples

iex> sensitive_values_from_body(%{"password" => "secret123"})
["secret123"]

update_credential(credential, attrs, actor)

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

Updates an existing credential and its credential bodies.

Parameters

  • credential - The credential to update
  • attrs - Map of attributes to update, including:
    • credential_bodies - List of credential body maps to create/update

Returns

  • {:ok, credential} - Successfully updated credential
  • {:error, error} - Error with update process

update_keychain_credential(keychain_credential, attrs, actor)

@spec update_keychain_credential(
  Lightning.Credentials.KeychainCredential.t(),
  map(),
  Lightning.Accounts.User.t()
) ::
  {:ok, Lightning.Credentials.KeychainCredential.t()}
  | {:error, Ecto.Changeset.t() | :unauthorized}

Updates a keychain credential on behalf of an actor.

Returns {:error, :unauthorized} when the actor may not edit it.

Examples

iex> update_keychain_credential(keychain_credential, %{name: "Updated"}, user)
{:ok, %KeychainCredential{}}

validate_credential_transfer(changeset, sender, credential)

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.