Merge branch 'develop' into feature/reports-groups-and-multiple-state-update
[akkoma] / lib / pleroma / plugs / rate_limiter.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Plugs.RateLimiter do
6 @moduledoc """
7
8 ## Configuration
9
10 A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
11
12 * The first element: `scale` (Integer). The time scale in milliseconds.
13 * The second element: `limit` (Integer). How many requests to limit in the time scale provided.
14
15 It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
16
17 To disable a limiter set its value to `nil`.
18
19 ### Example
20
21 config :pleroma, :rate_limit,
22 one: {1000, 10},
23 two: [{10_000, 10}, {10_000, 50}],
24 foobar: nil
25
26 Here we have three limiters:
27
28 * `one` which is not over 10req/1s
29 * `two` which has two limits: 10req/10s for unauthenticated users and 50req/10s for authenticated users
30 * `foobar` which is disabled
31
32 ## Usage
33
34 AllowedSyntax:
35
36 plug(Pleroma.Plugs.RateLimiter, :limiter_name)
37 plug(Pleroma.Plugs.RateLimiter, {:limiter_name, options})
38
39 Allowed options:
40
41 * `bucket_name` overrides bucket name (e.g. to have a separate limit for a set of actions)
42 * `params` appends values of specified request params (e.g. ["id"]) to bucket name
43
44 Inside a controller:
45
46 plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
47 plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
48
49 plug(
50 Pleroma.Plugs.RateLimiter,
51 {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
52 when action in ~w(fav_status unfav_status)a
53 )
54
55 or inside a router pipeline:
56
57 pipeline :api do
58 ...
59 plug(Pleroma.Plugs.RateLimiter, :one)
60 ...
61 end
62 """
63 import Pleroma.Web.TranslationHelpers
64 import Plug.Conn
65
66 alias Pleroma.User
67
68 def init(limiter_name) when is_atom(limiter_name) do
69 init({limiter_name, []})
70 end
71
72 def init({limiter_name, opts}) do
73 case Pleroma.Config.get([:rate_limit, limiter_name]) do
74 nil -> nil
75 config -> {limiter_name, config, opts}
76 end
77 end
78
79 # Do not limit if there is no limiter configuration
80 def call(conn, nil), do: conn
81
82 def call(conn, settings) do
83 case check_rate(conn, settings) do
84 {:ok, _count} ->
85 conn
86
87 {:error, _count} ->
88 render_throttled_error(conn)
89 end
90 end
91
92 defp bucket_name(conn, limiter_name, opts) do
93 bucket_name = opts[:bucket_name] || limiter_name
94
95 if params_names = opts[:params] do
96 params_values = for p <- Enum.sort(params_names), do: conn.params[p]
97 Enum.join([bucket_name] ++ params_values, ":")
98 else
99 bucket_name
100 end
101 end
102
103 defp check_rate(
104 %{assigns: %{user: %User{id: user_id}}} = conn,
105 {limiter_name, [_, {scale, limit}], opts}
106 ) do
107 bucket_name = bucket_name(conn, limiter_name, opts)
108 ExRated.check_rate("#{bucket_name}:#{user_id}", scale, limit)
109 end
110
111 defp check_rate(conn, {limiter_name, [{scale, limit} | _], opts}) do
112 bucket_name = bucket_name(conn, limiter_name, opts)
113 ExRated.check_rate("#{bucket_name}:#{ip(conn)}", scale, limit)
114 end
115
116 defp check_rate(conn, {limiter_name, {scale, limit}, opts}) do
117 check_rate(conn, {limiter_name, [{scale, limit}, {scale, limit}], opts})
118 end
119
120 def ip(%{remote_ip: remote_ip}) do
121 remote_ip
122 |> Tuple.to_list()
123 |> Enum.join(".")
124 end
125
126 defp render_throttled_error(conn) do
127 conn
128 |> render_error(:too_many_requests, "Throttled")
129 |> halt()
130 end
131 end