Picnic.Auth (Picnic v0.1.0)

Copy Markdown View Source

Login, password hashing, and the two-factor handshake.

Picnic's login endpoint expects the password as an MD5 hex digest — that is Picnic's scheme, not this library's choice; MD5 offers no real protection and the value should be treated exactly like the plaintext password. hash_password/1 also accepts an already-hashed value, and a previously obtained token can skip login entirely via Picnic.Client.new(auth_token: token).

The auth token is returned by the API in the x-picnic-auth response header and stored on the client struct; subsequent requests send it automatically.

Two-factor authentication

Accounts with 2FA enabled don't log in invisibly. login/4 returns {:error, %Picnic.Error{category: :auth, reason: :two_factor_required}} — note that it hands back an error, not a client. The error carries the provisional client needed to finish the handshake, so feed the login result straight into the 2FA calls:

result = Picnic.login(client, email, password)
# {:error, %Picnic.Error{reason: :two_factor_required}}

:ok = Picnic.Auth.generate_2fa(result)
{:ok, client} = Picnic.Auth.verify_2fa(result, code_from_sms)

Pass send_2fa: true to have login/4 request the code itself, which collapses the flow to two steps. It is opt-in because sending a code is a side effect — the default keeps one login to one request, and leaves the delivery channel your choice:

result = Picnic.login(client, email, password, send_2fa: true)
{:ok, client} = Picnic.Auth.verify_2fa(result, code_from_sms)

The provisional token is also on the error as error.raw.auth_token, for callers who need to carry it across processes rather than pipe the result.

Summary

Types

Anything generate_2fa/2 and verify_2fa/2 accept: a client, or the result of login/4 passed through unopened.

Functions

Requests a second-factor code to be sent. Options: :channel (default "SMS").

MD5-hashes a plaintext password into the lowercase hex digest Picnic's login endpoint expects. A value that already looks like an MD5 digest (32 hex characters) is passed through unchanged, so pre-hashed secrets can be stored instead of plaintext.

Logs in and returns a client carrying the auth token.

Verifies a second-factor code and returns the fully authenticated client.

Types

resumable()

@type resumable() ::
  Picnic.Client.t() | {:ok, Picnic.Client.t()} | {:error, Picnic.Error.t()}

Anything generate_2fa/2 and verify_2fa/2 accept: a client, or the result of login/4 passed through unopened.

Functions

generate_2fa(client_or_result, opts \\ [])

@spec generate_2fa(
  resumable(),
  keyword()
) :: :ok | {:error, Picnic.Error.t()}

Requests a second-factor code to be sent. Options: :channel (default "SMS").

Takes either a client carrying the provisional token or the login/4 result that carries it — see the module documentation. Any other error is returned unchanged, so a failed login propagates rather than being masked.

hash_password(password)

@spec hash_password(String.t()) :: String.t()

MD5-hashes a plaintext password into the lowercase hex digest Picnic's login endpoint expects. A value that already looks like an MD5 digest (32 hex characters) is passed through unchanged, so pre-hashed secrets can be stored instead of plaintext.

login(client, email, password, opts \\ [])

@spec login(Picnic.Client.t(), String.t(), String.t(), keyword()) ::
  {:ok, Picnic.Client.t()} | {:error, Picnic.Error.t()}

Logs in and returns a client carrying the auth token.

Returns {:error, %Picnic.Error{category: :auth, reason: :two_factor_required}} when the account needs a second factor; pipe that result into generate_2fa/2 and verify_2fa/2 to finish the handshake.

Options

  • :send_2fa — request the second-factor code as part of logging in, instead of leaving generate_2fa/2 to the caller. Defaults to false.
  • :channel — delivery channel for that code, default "SMS". Only used with send_2fa: true.

verify_2fa(client, code)

@spec verify_2fa(resumable(), String.t()) ::
  {:ok, Picnic.Client.t()} | {:error, Picnic.Error.t()}

Verifies a second-factor code and returns the fully authenticated client.

Like generate_2fa/2, takes a client or the login/4 result. A login that needed no second factor passes through as {:ok, client}, so the call is safe to leave in a pipeline; any other error is returned unchanged.