Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
commit
af6077b9c3
455 changed files with 78366 additions and 0 deletions
58
lib/tarakan_web/plugs/code_browser_rate_limit.ex
Normal file
58
lib/tarakan_web/plugs/code_browser_rate_limit.ex
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
defmodule TarakanWeb.Plugs.CodeBrowserRateLimit do
|
||||
@moduledoc """
|
||||
Per-IP budget for browser traffic, which includes the code browser.
|
||||
|
||||
Anonymous code browsing spawns git subprocesses against local mirrors and
|
||||
can enqueue outbound mirror fetches, so each client IP gets a generous
|
||||
request budget (default 300/min). IPv6 clients bucket by their /64
|
||||
prefix. Platform admins and moderators are exempt (operational tooling);
|
||||
the plug runs after the session scope is fetched so the exemption can see
|
||||
the current role.
|
||||
"""
|
||||
|
||||
import Plug.Conn
|
||||
|
||||
alias Tarakan.RateLimiter
|
||||
alias TarakanWeb.Plugs.ClientIp
|
||||
|
||||
@defaults [limit: 300, window_seconds: 60]
|
||||
@unlimited_roles ~w(admin moderator)
|
||||
|
||||
@doc false
|
||||
def init(opts) do
|
||||
configured = Application.get_env(:tarakan, __MODULE__, [])
|
||||
@defaults |> Keyword.merge(configured) |> Keyword.merge(opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def call(conn, opts) do
|
||||
if unlimited_actor?(conn) do
|
||||
conn
|
||||
else
|
||||
limit = Keyword.fetch!(opts, :limit)
|
||||
window_seconds = Keyword.fetch!(opts, :window_seconds)
|
||||
key = {:code_browser_ip, ClientIp.remote_ip_bucket(conn)}
|
||||
|
||||
case RateLimiter.check(key, limit, window_seconds) do
|
||||
:ok -> conn
|
||||
{:error, _reason, retry_after} -> reject(conn, retry_after)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp unlimited_actor?(conn) do
|
||||
case conn.assigns[:current_scope] do
|
||||
%{platform_role: role} when role in @unlimited_roles -> true
|
||||
%{account: %{platform_role: role}} when role in @unlimited_roles -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp reject(conn, retry_after) do
|
||||
conn
|
||||
|> put_resp_header("retry-after", Integer.to_string(retry_after))
|
||||
|> put_resp_content_type("text/plain")
|
||||
|> send_resp(429, "too many requests")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue