logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

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