README.md

# APIacFilterThrottler

An `APIac.Filter` plug for API requests rate-limiting

This plug uses the [Exhammer](https://github.com/ExHammer/hammer) package as
its backend. This library uses the token bucket algorithm, which means that
this plug is mainly suitable for limiting abuses, not for accurate rate limiting. By
default, a local ETS backend is launched on startup.

```elixir
def deps do
  [
    {:apiac_filter_throttler, "~> 1.0"}
  ]
end
```

## Plug options

- `key`: a
`(Plug.Conn.t -> String.t | {String.t, non_neg_integer(), non_neg_integer()})`
function, taking in parameter the connection and returning either the key, or the
tuple `{key, scale, limit}`. No default value.
Note that the `APIacFilterThrottler.Functions` provides with out-of-the-box functions
- `scale`: the time window of the token bucket algorithm, in milliseconds. No default value.
- `limit`: the maximum limit of the token bucket algorithm, in attempt count. No default value.
- `increment`: the increment of the token bucket algorithm (defaults to `1`)
- `backend`: Exhammer's backend, defaults to `nil`
- `exec_cond`: a `(Plug.Conn.t() -> boolean())` function that determines whether
this filter is to be executed or not. Defaults to `fn _ -> true end`
- `send_error_response`: function called when request is throttled. Defaults to
`APIacFilterThrottler.send_error_response/3`
- `error_response_verbosity`: one of `:debug`, `:normal` or `:minimal`.
Defaults to `:normal`

## Example

Allow 50 request / 10 seconds per subject and per client:

```elixir
plug APIacFilterThrottler, key: &APIacFilterThrottler.Functions.throttle_by_subject_client/1,
  scale: 10_000,
  limit: 50
```

Allow 5000 requests / minute per client, only for machine-to-machine access:

```elixir
plug APIacFilterThrottler, key: &APIacFilterThrottler.Functions.throttle_by_client/1,
  exec_cond: &APIac.machine_to_machine?/1,
  scale: 60_000,
  limit: 5000
```

## Security considerations

Consider the risk of collisions when constructing the key> For instance, a key function
concatenating the ip address and a subject (username) would return the same key
("72.23.241.121edwards") for:
- a user "edwards" connecting from 72.23.241.121
- a user "1edwards" connecting from 72.23.241.12

The more control an attacker has on choosing the key parameters (e.g. the username), the
easier to find a collision.

Finding a collision can result in a DOS for the legitimate requester.

Using a hash function such as `:erlang.phash2/1`, MD5, etc. cam help mitigate the risk,
at the expense of performance. Also note that `:erlang.phash2/1` is not a
collision-resistant hash function (as results are not uniformly distributed).