Fixed ArgumentError in rate limiter by replacing cond statement with proper case pattern matching. The cond was incorrectly evaluating assignment expressions as truthy values instead of checking if headers were empty lists. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
78 lines
1.8 KiB
Elixir
78 lines
1.8 KiB
Elixir
defmodule AprsmeWeb.Plugs.RateLimiter do
|
|
@moduledoc """
|
|
Rate limiting plug to prevent DoS attacks
|
|
"""
|
|
import Phoenix.Controller
|
|
import Plug.Conn
|
|
|
|
def init(opts) do
|
|
# Default options
|
|
Keyword.merge(
|
|
[
|
|
# 1 minute
|
|
scale: 60_000,
|
|
# 100 requests per minute
|
|
limit: 100,
|
|
# Rate limit by IP address
|
|
key: :ip,
|
|
error_message: "Too many requests"
|
|
],
|
|
opts
|
|
)
|
|
end
|
|
|
|
def call(conn, opts) do
|
|
key = get_key(conn, opts[:key])
|
|
scale = opts[:scale]
|
|
limit = opts[:limit]
|
|
error_message = opts[:error_message]
|
|
|
|
case Aprsme.RateLimiter.hit("rate_limit:#{key}", scale, limit) do
|
|
{:allow, _count} ->
|
|
conn
|
|
|
|
{:deny, _retry_after} ->
|
|
conn
|
|
|> put_status(:too_many_requests)
|
|
|> json(%{error: error_message})
|
|
|> halt()
|
|
end
|
|
end
|
|
|
|
defp get_key(conn, :ip) do
|
|
# Check headers in order of preference
|
|
case {get_req_header(conn, "cf-connecting-ip"), get_req_header(conn, "x-forwarded-for"),
|
|
get_req_header(conn, "x-real-ip")} do
|
|
# Cloudflare header takes precedence
|
|
{[cf | _], _, _} ->
|
|
cf
|
|
|
|
# Then standard X-Forwarded-For header
|
|
{[], [forwarded | _], _} ->
|
|
forwarded |> String.split(",") |> List.first() |> String.trim()
|
|
|
|
# Then X-Real-IP header
|
|
{[], [], [real | _]} ->
|
|
real
|
|
|
|
# Fall back to remote_ip
|
|
{[], [], []} ->
|
|
conn.remote_ip |> :inet.ntoa() |> to_string()
|
|
end
|
|
end
|
|
|
|
defp get_key(conn, :user_agent) do
|
|
case get_req_header(conn, "user-agent") do
|
|
[ua | _] -> ua
|
|
[] -> "unknown"
|
|
end
|
|
end
|
|
|
|
defp get_key(conn, custom_key) when is_function(custom_key) do
|
|
custom_key.(conn)
|
|
end
|
|
|
|
defp get_key(_conn, key) when is_binary(key) do
|
|
key
|
|
end
|
|
end
|