Current section

Files

Jump to
openmaize lib openmaize onetime_pass.ex
Raw

lib/openmaize/onetime_pass.ex

defmodule Openmaize.OnetimePass do
@moduledoc """
Module to handle one-time passwords for use in two factor authentication.
There is one option to set the database module used:
* db_module - the module that is used to query the database
* the default is MyApp.OpenmaizeEcto - the name of the module generated by `mix openmaize.gen.ectodb`
* if you implement your own database module, it needs to implement the Openmaize.Database behaviour
There are also the following options for the one-time passwords:
* HMAC-based one-time passwords
* token_length - the length of the one-time password
* the default is 6
* last - the count when the one-time password was last used
* this count needs to be stored server-side
* window - the number of future attempts allowed
* the default is 3
* Time-based one-time passwords
* token_length - the length of the one-time password
* the default is 6
* interval_length - the length of each timed interval
* the default is 30 (seconds)
* window - the number of attempts, before and after the current one, allowed
* the default is 1 (1 interval before and 1 interval after)
See the documentation for the Comeonin.Otp module for more details
about generating and verifying one-time passwords.
## Examples
Add the following line to your controller to call OnetimePass with the
default values:
plug Openmaize.OnetimePass when action in [:login_twofa]
And to set the token length to 8 characters:
plug Openmaize.OnetimePass, [token_length: 8] when action in [:login_twofa]
"""
@behaviour Plug
import Plug.Conn
alias Comeonin.Otp
def init(opts) do
Keyword.pop opts, :db_module, Openmaize.Utils.default_db
end
@doc """
Handle the one-time password POST request.
If the one-time password check is successful, the user will be added
to the session.
"""
def call(%Plug.Conn{params: %{"user" => %{"id" => id} = user_params}} = conn,
{db_module, opts}) do
db_module.find_user_by_id(id)
|> check_key(user_params, opts)
|> handle_auth(conn)
end
defp check_key(user, %{"hotp" => hotp}, opts) do
{user, Otp.check_hotp(hotp, user.otp_secret, [last: user.otp_last] ++ opts)}
end
defp check_key(user, %{"totp" => totp}, opts) do
{user, Otp.check_totp(totp, user.otp_secret, opts)}
end
defp handle_auth({_, false}, conn) do
put_private(conn, :openmaize_error, "Invalid credentials")
end
defp handle_auth({%{otp_last: otp_last} = user, last}, conn)
when last > otp_last do
put_private(conn, :openmaize_user, %{user | otp_last: last})
end
defp handle_auth(_, conn) do
put_private(conn, :openmaize_error, "Invalid credentials")
end
end