rate_limiter.ex (3818B)
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