This repository has been archived by the owner on Jan 19, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 30
/
onetime_pass.ex
107 lines (86 loc) · 3.59 KB
/
onetime_pass.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
defmodule Openmaize.OnetimePass do
@moduledoc """
Module to handle one-time passwords for use in two factor authentication.
`Openmaize.OnetimePass` checks the one-time password, and returns an
`openmaize_user` message (the user model) if the one-time password is
correct or an `openmaize_error` message if there is an error.
After this function has been called, you need to add the user to the
session, by running `put_session(conn, :user_id, id)`, or send an API
token to the user.
## Options
There are two options related to the database - in most cases you
will not need to change the `repo` and `user_model` options:
* repo - the name of the repo
* the default is MyApp.Repo - using the name of the project
* user_model - the name of the user model
* the default is MyApp.User - using the name of the project
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
alias Openmaize.Database, as: DB
alias Openmaize.{Config, Log}
@doc false
def init(opts) do
{Keyword.get(opts, :repo, Openmaize.Utils.default_repo),
Keyword.get(opts, :user_model, Openmaize.Utils.default_user_model),
opts}
end
@doc false
def call(%Plug.Conn{params: %{"user" => %{"id" => id, "hotp" => hotp}}} = conn,
{repo, user_model, opts}) do
{:ok, result} = repo.transaction(fn ->
DB.get_user_with_lock(repo, user_model, id)
|> check_hotp(hotp, opts)
|> DB.update_otp(repo)
end)
handle_auth(result, conn, id)
end
def call(%Plug.Conn{params: %{"user" => %{"id" => id, "totp" => totp}}} = conn,
{repo, user_model, opts}) do
repo.get(user_model, id)
|> check_totp(totp, opts)
|> DB.update_otp(repo)
|> handle_auth(conn, id)
end
defp check_hotp(user, hotp, opts) do
{user, Otp.check_hotp(hotp, user.otp_secret, [last: user.otp_last] ++ opts)}
end
defp check_totp(user, totp, opts) do
{user, Otp.check_totp(totp, user.otp_secret, opts)}
end
defp handle_auth({:error, message}, conn, user_id) do
Log.log(:warn, Config.log_level, conn.request_path,
%Log{user: user_id, message: message})
put_private(conn, :openmaize_error, "Invalid credentials")
end
defp handle_auth(user, conn, user_id) do
Log.log(:info, Config.log_level, conn.request_path,
%Log{user: user_id,
message: "successful one-time password login"})
put_private(conn, :openmaize_user, Map.drop(user, Config.drop_user_keys))
end
end