Created
August 16, 2019 09:50
-
-
Save yordis/6a26459f21ea249dfcb6d2af0c633a52 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
alias API.{ErrorView, Redis} | |
defmodule API.RateLimitPlug do | |
@moduledoc """ | |
A plug that rate limits authenticated requests by the minute. | |
""" | |
import Plug.Conn | |
import Phoenix.Controller | |
@max_per_minute 120 | |
@doc """ | |
Initialize the plug. | |
""" | |
@spec init(Plug.opts) :: Plug.opts | |
def init(opts), do: opts | |
@doc """ | |
Perform rate limiting on a connection if it is authenticated. | |
If it is not authenticated, allow it to pass through. | |
""" | |
@spec call(Plug.Conn.t, Plug.opts) :: Plug.Conn.t | |
def call(conn, opts) do | |
if conn.private.current_account do | |
do_call(conn, opts) | |
else | |
conn | |
end | |
end | |
# Perform rate limiting on an authenticated conn. | |
@spec do_call(Plug.Conn.t, Plug.opts) :: Plug.Conn.t | |
defp do_call(conn, _opts) do | |
cache_key = conn |> cache_key | |
count = | |
cache_key | |
|> Redis.get! | |
|> Kernel.||("0") | |
|> String.to_integer | |
|> Kernel.+(1) | |
cache_key |> Redis.setex!(70, count) | |
if count <= @max_per_minute do | |
conn | |
else | |
conn |> rate_limit_exceeded | |
end | |
end | |
# Get the cache key for a given connection. | |
@spec cache_key(Plug.Conn.t) :: String.t | |
defp cache_key(conn) do | |
"#{conn.private.current_account.id}:#{current_minute_ts}" | |
end | |
# Get a timestamp representing the current minute. | |
@spec current_minute_ts :: String.t | |
defp current_minute_ts do | |
now = DateTime.utc_now | |
"#{now.year}-#{now.month}-#{now.day}T#{now.hour}:#{now.minute}" | |
end | |
# Halt the connection and send an error. | |
@spec rate_limit_exceeded(Plug.Conn.t) :: Plug.Conn.t | |
defp rate_limit_exceeded(conn) do | |
conn | |
|> halt | |
|> put_status(:too_many_requests) | |
|> render(ErrorView, "429.json", detail: """ | |
Rate limit exceeded. Maximum number of requests per minute may not exceed \ | |
#{@max_per_minute}.\ | |
""") | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment