-
-
Notifications
You must be signed in to change notification settings - Fork 152
/
plug.ex
186 lines (150 loc) · 5.3 KB
/
plug.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
defmodule PowResetPassword.Plug do
@moduledoc """
Plug helper methods.
"""
alias Plug.Conn
alias Pow.{Config, Plug, Store.Backend.EtsCache, UUID}
alias PowResetPassword.Ecto.Context, as: ResetPasswordContext
alias PowResetPassword.Store.ResetTokenCache
@doc """
Creates a changeset from the user fetched in the connection.
"""
@spec change_user(Conn.t(), map()) :: map()
def change_user(conn, params \\ %{}) do
user = reset_password_user(conn) || user_struct(conn)
user.__struct__.reset_password_changeset(user, params)
end
defp user_struct(conn) do
conn
|> Plug.fetch_config()
|> Config.user!()
|> struct()
end
# TODO: Remove by 1.1.0
@doc false
@deprecated "No longer used"
def assign_reset_password_user(conn, user) do
Conn.assign(conn, :reset_password_user, user)
end
@doc """
Finds a user for the provided params, creates a token, and stores the user
for the token.
The returned `:token` is signed for public consumption using
`Pow.Plug.sign_token/4`. Additionally `Pow.UUID.generate/0` is called whether
the user exists or not to prevent timing attacks.
`:reset_password_token_store` can be passed in the config for the conn. This
value defaults to
`{PowResetPassword.Store.ResetTokenCache, backend: Pow.Store.Backend.EtsCache}`.
The `Pow.Store.Backend.EtsCache` backend store can be changed with the
`:cache_store_backend` option.
"""
@spec create_reset_token(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, map(), Conn.t()}
def create_reset_token(conn, params) do
config = Plug.fetch_config(conn)
token = UUID.generate()
params
|> Map.get("email")
|> ResetPasswordContext.get_by_email(config)
|> case do
nil ->
{:error, change_user(conn, params), conn}
user ->
{store, store_config} = store(config)
store.put(store_config, token, user)
signed = Plug.sign_token(conn, signing_salt(), token, config)
{:ok, %{token: signed, user: user}, conn}
end
end
defp signing_salt(), do: Atom.to_string(__MODULE__)
@doc """
Verifies the signed token and fetches user from store.
If a user is found, it'll be assigned to `conn.assigns` for key
`:reset_password_user`.
A `:pow_reset_password_decoded_token` key will be assigned in `conn.private`
with the decoded token. This is used to invalidate the token when calling
`update_user_password/2`.
The token will be decoded and verified with `Pow.Plug.verify_token/4`.
See `create_reset_token/2` for more on `:reset_password_token_store` config
option.
"""
@spec load_user_by_token(Conn.t(), binary()) :: {:ok, Conn.t()} | {:error, Conn.t()}
def load_user_by_token(conn, signed_token) do
config = Plug.fetch_config(conn)
with {:ok, token} <- Plug.verify_token(conn, signing_salt(), signed_token, config),
user when not is_nil(user) <- fetch_user_from_token(token, config) do
conn =
conn
|> Conn.put_private(:pow_reset_password_decoded_token, token)
|> Conn.assign(:reset_password_user, user)
{:ok, conn}
else
_any -> {:error, conn}
end
end
defp fetch_user_from_token(token, config) do
{store, store_config} = store(config)
store_config
|> store.get(token)
|> case do
:not_found -> nil
user -> user
end
end
# TODO: Remove by 1.1.0
@doc false
@deprecated "Use `load_user_by_token/2` instead"
def user_from_token(conn, token) do
{store, store_config} =
conn
|> Plug.fetch_config()
|> store()
store_config
|> store.get(token)
|> case do
:not_found -> nil
user -> user
end
end
@doc """
Updates the password for the user fetched in the connection.
The user should exist in `conn.assigns` for key `:reset_password_user` and
the decoded token in `conn.private` for key
`:pow_reset_password_decoded_token`. `load_user_by_token/2` will ensure this.
See `create_reset_token/2` for more on `:reset_password_token_store` config
option.
"""
@spec update_user_password(Conn.t(), map()) :: {:ok, map(), Conn.t()} | {:error, map(), Conn.t()}
def update_user_password(conn, params) do
config = Plug.fetch_config(conn)
conn
|> reset_password_user()
|> ResetPasswordContext.update_password(params, config)
|> case do
{:ok, user} ->
expire_token(conn, config)
{:ok, user, conn}
{:error, changeset} ->
{:error, changeset, conn}
end
end
defp expire_token(%{private: %{pow_reset_password_decoded_token: token}}, config) do
{store, store_config} = store(config)
store.delete(store_config, token)
end
defp expire_token(_conn, _config) do
IO.warn("no `:pow_reset_password_decoded_token` key found in `conn.private`, please call `#{inspect __MODULE__}.load_user_by_token/2` first")
:ok
end
defp reset_password_user(conn) do
conn.assigns[:reset_password_user]
end
defp store(config) do
case Config.get(config, :reset_password_token_store) do
{store, store_config} -> {store, store_opts(config, store_config)}
nil -> {ResetTokenCache, store_opts(config)}
end
end
defp store_opts(config, store_config \\ []) do
Keyword.put_new(store_config, :backend, Config.get(config, :cache_store_backend, EtsCache))
end
end