2020-07-21 16:50:33 +02:00
|
|
|
defmodule PhilomenaWeb.LimitPlug do
|
|
|
|
@moduledoc """
|
|
|
|
This plug automatically limits requests which are submitted faster
|
|
|
|
than should be allowed for a given client.
|
|
|
|
|
|
|
|
## Example
|
|
|
|
|
|
|
|
plug PhilomenaWeb.LimitPlug, [time: 30, error: "Too fast! Slow down."]
|
|
|
|
"""
|
|
|
|
|
|
|
|
alias Plug.Conn
|
|
|
|
alias Phoenix.Controller
|
2020-07-24 14:51:23 +02:00
|
|
|
alias Philomena.Users.User
|
2020-07-21 16:50:33 +02:00
|
|
|
|
|
|
|
@doc false
|
|
|
|
@spec init(any()) :: any()
|
|
|
|
def init(opts), do: opts
|
|
|
|
|
|
|
|
@doc false
|
|
|
|
@spec call(Conn.t(), any()) :: Conn.t()
|
|
|
|
def call(conn, opts) do
|
|
|
|
limit = Keyword.get(opts, :limit, 1)
|
|
|
|
time = Keyword.get(opts, :time, 5)
|
|
|
|
error = Keyword.get(opts, :error)
|
2020-09-24 15:36:39 +02:00
|
|
|
skip_staff = Keyword.get(opts, :skip_staff, true)
|
2020-07-21 16:50:33 +02:00
|
|
|
|
|
|
|
data = [
|
|
|
|
current_user_id(conn.assigns.current_user),
|
|
|
|
:inet_parse.ntoa(conn.remote_ip),
|
|
|
|
conn.private.phoenix_action,
|
|
|
|
conn.private.phoenix_controller
|
|
|
|
]
|
|
|
|
|
|
|
|
key = "rl-#{Enum.join(data, "")}"
|
2020-09-15 02:52:02 +02:00
|
|
|
amt = Redix.command!(:redix, ["GET", key]) || 0
|
2020-07-21 16:50:33 +02:00
|
|
|
|
2020-09-15 02:52:02 +02:00
|
|
|
conn = increment_after_post(conn, key, time)
|
2020-07-21 16:50:33 +02:00
|
|
|
|
|
|
|
cond do
|
|
|
|
amt <= limit ->
|
|
|
|
conn
|
|
|
|
|
2020-09-24 15:36:39 +02:00
|
|
|
is_staff(conn.assigns.current_user) and skip_staff ->
|
2020-07-24 14:51:23 +02:00
|
|
|
conn
|
|
|
|
|
2020-08-11 07:14:00 +02:00
|
|
|
ajax?(conn) ->
|
|
|
|
conn
|
|
|
|
|> Controller.put_flash(:error, error)
|
|
|
|
|> Conn.send_resp(:multiple_choices, "")
|
|
|
|
|> Conn.halt()
|
|
|
|
|
2020-08-22 18:11:50 +02:00
|
|
|
api?(conn) ->
|
|
|
|
conn
|
|
|
|
|> Conn.put_status(:too_many_requests)
|
|
|
|
|> Controller.text("")
|
|
|
|
|> Conn.halt()
|
|
|
|
|
2020-07-21 16:50:33 +02:00
|
|
|
true ->
|
|
|
|
conn
|
|
|
|
|> Controller.put_flash(:error, error)
|
|
|
|
|> Controller.redirect(external: conn.assigns.referrer)
|
|
|
|
|> Conn.halt()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-07-24 14:51:23 +02:00
|
|
|
defp is_staff(%User{role: "admin"}), do: true
|
|
|
|
defp is_staff(%User{role: "moderator"}), do: true
|
|
|
|
defp is_staff(%User{role: "assistant"}), do: true
|
|
|
|
defp is_staff(_), do: false
|
|
|
|
|
2020-07-21 16:50:33 +02:00
|
|
|
defp current_user_id(%{id: id}), do: id
|
|
|
|
defp current_user_id(_), do: nil
|
2020-08-11 07:14:00 +02:00
|
|
|
|
2020-08-22 18:11:50 +02:00
|
|
|
defp api?(conn) do
|
|
|
|
case conn.path_info do
|
|
|
|
["api" | _] -> true
|
|
|
|
_ -> false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-11 07:14:00 +02:00
|
|
|
defp ajax?(conn) do
|
|
|
|
case Conn.get_req_header(conn, "x-requested-with") do
|
|
|
|
[value] -> String.downcase(value) == "xmlhttprequest"
|
|
|
|
_ -> false
|
|
|
|
end
|
|
|
|
end
|
2020-09-15 02:52:02 +02:00
|
|
|
|
|
|
|
defp increment_after_post(conn, key, time) do
|
|
|
|
Conn.register_before_send(conn, fn conn ->
|
|
|
|
# Phoenix status returns 200 for form validation errors
|
|
|
|
if conn.status != 200 do
|
|
|
|
Redix.pipeline!(:redix, [
|
|
|
|
["INCR", key],
|
|
|
|
["EXPIRE", key, time]
|
|
|
|
])
|
|
|
|
end
|
|
|
|
|
|
|
|
conn
|
|
|
|
end)
|
|
|
|
end
|
2020-07-21 16:50:33 +02:00
|
|
|
end
|