View Source Plausible.Auth.TOTP (Plausible v0.0.1)
TOTP auth context
Handles all the aspects of TOTP setup, management and validation for users.
Setup
TOTP setup is started with initiate/1
. At this stage, a random secret
binary is generated for user and stored under User.totp_secret
. The secret
is additionally encrypted while stored in the database using Cloak
. The
vault for safe storage is configured in Plausible.Auth.TOTP.Vault
via
a dedicated Ecto
type defined in Plausible.Auth.TOTP.EncryptedBinary
.
The function returns updated user along with TOTP URI and a readable form
of secret. Both - the URI and readable secret - are meant for exposure
in the user's setup screen. The URI should be encoded as a QR code.
After initiation, user is expected to confirm valid setup with enable/2
,
providing TOTP code from their authenticator app. After code validation
passes successfully, the User.totp_enabled
flag is set to true
.
Finally, the user must be immediately presented with a list of recovery codes
generated with generate_recovery_codes/1
. The codes should be presented
in copy/paste friendly form, ideally also with a print-friendly view option.
The function can be run more than once, giving the user ability to regenerate
codes from the final stage of setup if needed.
The initiate/1
and enable/1
functions can be safely called multiple
times, allowing user to abort and restart setup up to these stages.
Management
State of TOTP for a particular user can be chcecked by calling enabled?/1
.
TOTP can be disabled with disable/2
. User is expected to provide their
current password for safety. Once disabled, all TOTP user settings are
cleared and any remaining generated recovery codes are removed. The function
can be safely run more than once.
If the user needs to regenerate the recovery codes outside of setup procedure,
they must do it via generate_recovery_codes_protected/2
, providing
their current password for safety. They must be warned that any existing
recovery codes will be invalidated.
Validation
After logging in, user's TOTP state must be checked with enabled?/1
.
If enabled, user must be presented with TOTP code input form accepting
6 digit characters. The code must be checked using validate_code/2
.
User must have an option to alternatively input one of their recovery
codes. Those codes must be checked with use_recovery_code/2
.
Code validity
In case of TOTP codes, a grace period of 30 seconds is applied, which
allows user to use their current and previous TOTP code, assuming 30
second validity window of each. This allows user to use code that was
about to expire before the submission. Regardless of that, each TOTP
code can be used only once. Validation procedure rejects repeat use
of the same code for safety. It's done by tracking last time a TOTP
code was used successfully, stored under User.totp_last_used_at
.
In case of recovery codes, each code is deleted immediately after use. They are strictly one-time use only.
Summary
Functions
@spec disable(Plausible.Auth.User.t(), String.t()) :: {:ok, Plausible.Auth.User.t()} | {:error, :invalid_password}
@spec enable(Plausible.Auth.User.t(), String.t(), Keyword.t()) :: {:ok, Plausible.Auth.User.t()} | {:error, :invalid_code | :not_initiated}
@spec enabled?(Plausible.Auth.User.t()) :: boolean()
@spec generate_recovery_codes(Plausible.Auth.User.t()) :: {:ok, [String.t()]} | {:error, :not_enabled}
@spec generate_recovery_codes_protected(Plausible.Auth.User.t(), String.t()) :: {:ok, [String.t()]} | {:error, :invalid_password | :not_enabled}
@spec initiate(Plausible.Auth.User.t()) :: {:ok, Plausible.Auth.User.t(), %{totp_uri: String.t(), secret: String.t()}} | {:error, :not_verified | :already_setup}
@spec use_recovery_code(Plausible.Auth.User.t(), String.t()) :: :ok | {:error, :invalid_code | :not_enabled}