feat: add rate limiting to admin endpoints (100 req/min)

- Add :admin type to RateLimit plug with 100 requests per minute per IP
- Apply rate limiting to all /admin routes (LiveView and controller actions)
- Protects superuser endpoints from abuse and brute force attempts
- Aligns with existing auth (10 req/min) and API (1000 req/min) limits
This commit is contained in:
Graham McIntire 2026-03-05 14:09:20 -06:00
parent d3d5d1e706
commit 8f52d87854
No known key found for this signature in database
5 changed files with 25 additions and 7 deletions

View file

@ -1,4 +1,12 @@
2026-03-05
feat: add rate limiting to admin endpoints (100 req/min)
- Add :admin type to RateLimit plug with 100 requests per minute per IP
- Apply rate limiting to all /admin routes (LiveView and controller actions)
- Protects superuser endpoints from abuse and brute force attempts
- Aligns with existing auth (10 req/min) and API (1000 req/min) rate limits
Files: lib/towerops_web/plugs/rate_limit.ex,
lib/towerops_web/router.ex
feat: add TowerOps suffix to all page titles for better tab identification
- Page titles now show "Page Title | TowerOps" format
- Helps users identify TowerOps tabs when multiple tabs are open

View file

@ -4,7 +4,8 @@ defmodule ToweropsWeb.Plugs.RateLimit do
Applies different rate limits based on the type of endpoint:
- Auth endpoints (login, register, password reset): 10 requests per minute per IP
- API endpoints: 100 requests per minute per IP
- API endpoints: 1000 requests per minute per IP
- Admin endpoints: 100 requests per minute per IP (superuser access)
Uses the client's real IP address from X-Forwarded-For or X-Real-IP headers
when behind a proxy/load balancer.
@ -21,13 +22,16 @@ defmodule ToweropsWeb.Plugs.RateLimit do
@api_limit 1000
@api_period to_timeout(minute: 1)
@type limit_type :: :auth | :api
@admin_limit 100
@admin_period to_timeout(minute: 1)
@type limit_type :: :auth | :api | :admin
def init(opts) do
type = Keyword.get(opts, :type, :api)
if type not in [:auth, :api] do
raise ArgumentError, "RateLimit plug :type must be :auth or :api, got: #{inspect(type)}"
if type not in [:auth, :api, :admin] do
raise ArgumentError, "RateLimit plug :type must be :auth, :api, or :admin, got: #{inspect(type)}"
end
type
@ -68,6 +72,7 @@ defmodule ToweropsWeb.Plugs.RateLimit do
defp limits_for(:auth), do: {@auth_limit, @auth_period}
defp limits_for(:api), do: {@api_limit, @api_period}
defp limits_for(:admin), do: {@admin_limit, @admin_period}
defp get_remote_ip(conn) do
# Try X-Forwarded-For first (set by Traefik and other proxies)

View file

@ -275,8 +275,12 @@ defmodule ToweropsWeb.Router do
## Admin routes (superuser only)
pipeline :admin_rate_limit do
plug RateLimit, type: :admin
end
scope "/", ToweropsWeb do
pipe_through [:browser, :require_authenticated_user, :require_superuser]
pipe_through [:browser, :require_authenticated_user, :require_superuser, :admin_rate_limit]
post "/admin/impersonate/:user_id", AdminController, :start_impersonate
delete "/admin/impersonate", AdminController, :stop_impersonate
@ -292,7 +296,7 @@ defmodule ToweropsWeb.Router do
{ToweropsWeb.UserAuth, :handle_policy_consent}
] do
scope "/admin", ToweropsWeb.Admin do
pipe_through [:browser, :require_authenticated_user, :require_superuser]
pipe_through [:browser, :require_authenticated_user, :require_superuser, :admin_rate_limit]
live "/", DashboardLive, :index
live "/users", UserLive.Index, :index

View file

@ -2,6 +2,7 @@
* Enhanced security for API endpoints and file uploads
* Improved validation for mobile device registration
* Better protection against potential DoS attacks
* Added rate limiting to admin endpoints
* Strengthened authentication requirements for sensitive operations
* Enhanced error messages to prevent information disclosure

View file

@ -31,7 +31,7 @@ defmodule ToweropsWeb.Plugs.RateLimitTest do
end
test "raises on invalid type" do
assert_raise ArgumentError, ~r/must be :auth or :api/, fn ->
assert_raise ArgumentError, ~r/must be :auth, :api, or :admin/, fn ->
RateLimit.init(type: :invalid)
end
end