/
reauthorization.ex
161 lines (128 loc) · 4.8 KB
/
reauthorization.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
defmodule PowAssent.Plug.Reauthorization do
@moduledoc """
This plug can reauthorize a user who signed in through a provider.
The plug is dependent on a `:handler` that has the following methods:
* `reauthorize?/2` - verifies the request for reauthorization condition. If
the condition exists for the request (usually the sign in path), the
reauthorization cookie will be fetched and deleted, the `reauthorize/2`
callback will be called, and the connection halted.
* `clear_reauthorization?/2` - verifies the request for clear reauthorization
condition. If the condition exists (usually the session delete path) then
the cookie is deleted.
* `reauthorize/3` - the callback to handle the request when a reauthorization
condition exists. Usually this would redirect the user.
See `PowAssent.Phoenix.ReauthorizationPlugHandler` for a Phoenix example.
## Example
plug PowAssent.Plug.Reauthorization,
handler: MyApp.ReauthorizationHandler
## Configuration options
* `:handler` - the handler module. Should either be a module or a tuple
`{module, options}`.
* `:reauthorization_cookie_key` - reauthorization key name. This defaults
to "authorization_provider". If `:otp_app` is used it'll automatically
prepend the key with the `:otp_app` value.
* `:reauthorization_cookie_opts` - keyword list of cookie options, see
`Plug.Conn.put_resp_cookie/4` for options. The default options are
`[max_age: max_age, path: "/"]` where `:max_age` is 30 days.
"""
alias Plug.Conn
alias Pow.Config
alias Pow.Plug, as: PowPlug
alias PowAssent.Plug
@cookie_key "reauthorization_provider"
@cookie_max_age Integer.floor_div(:timer.hours(24) * 30, 1000)
@doc false
@spec init(Config.t()) :: {Config.t(), {module(), Config.t()}}
def init(config) do
handler = get_handler(config)
config = Keyword.delete(config, :handler)
{config, handler}
end
defp get_handler(plug_config) do
{handler, config} =
plug_config
|> Config.get(:handler)
|> Kernel.||(raise_no_handler())
|> case do
{handler, config} -> {handler, config}
handler -> {handler, []}
end
{handler, Keyword.put(config, :reauthorization_plug, __MODULE__)}
end
@doc false
@spec call(Conn.t(), {Config.t(), {module(), Config.t()}}) :: Conn.t()
def call(conn, {config, {handler, handler_config}}) do
config =
conn
|> Plug.fetch_config()
|> Config.merge(config)
conn =
conn
|> Conn.fetch_cookies()
|> Plug.put_create_session_callback(&store_reauthorization_provider/3)
provider = get_reauthorization_provider(conn, {handler, handler_config}, config)
cond do
provider ->
conn
|> clear_cookie(config)
|> handler.reauthorize(provider, handler_config)
|> Conn.halt()
clear_reauthorization?(conn, {handler, handler_config}) ->
clear_cookie(conn, config)
true ->
conn
end
end
defp store_reauthorization_provider(conn, provider, config) do
Conn.register_before_send(conn, &Conn.put_resp_cookie(&1, cookie_key(config), provider, cookie_opts(config)))
end
defp cookie_key(config) do
Config.get(config, :reauthorization_cookie_key, default_cookie_key(config))
end
defp default_cookie_key(config) do
PowPlug.prepend_with_namespace(config, @cookie_key)
end
defp cookie_opts(config) do
config
|> Config.get(:reauthorization_cookie_opts, [])
|> Keyword.put_new(:max_age, @cookie_max_age)
|> Keyword.put_new(:path, "/")
end
defp get_reauthorization_provider(conn, {handler, handler_config}, config) do
with :ok <- check_should_reauthorize(conn, {handler, handler_config}),
{:ok, provider} <- fetch_provider_from_cookie(conn, config) do
provider
else
:error -> nil
end
end
defp check_should_reauthorize(conn, {handler, handler_config}) do
case handler.reauthorize?(conn, handler_config) do
true -> :ok
false -> :error
end
end
defp fetch_provider_from_cookie(conn, config) do
case conn.cookies[cookie_key(config)] do
nil ->
:error
provider ->
config
|> Plug.available_providers()
|> Enum.any?(&Atom.to_string(&1) == provider)
|> case do
true -> {:ok, provider}
false -> :error
end
end
end
defp clear_cookie(conn, config) do
Conn.put_resp_cookie(conn, cookie_key(config), "", max_age: -1)
end
defp clear_reauthorization?(conn, {handler, handler_config}),
do: handler.clear_reauthorization?(conn, handler_config)
@spec raise_no_handler :: no_return
defp raise_no_handler do
Config.raise_error("No :handler configuration option provided. It's required to set this when using #{inspect __MODULE__}.")
end
end