From d431931cfede7f4b961e4669c9ab32a6585b4a50 Mon Sep 17 00:00:00 2001
From: Graham McIntire
Date: Sun, 8 Feb 2026 10:08:31 -0600
Subject: [PATCH] update hammer
---
config/config.exs | 4 ----
lib/towerops/application.ex | 2 +-
lib/towerops/rate_limit.ex | 10 ++++++++++
lib/towerops_web/live/admin/monitoring_live.html.heex | 4 +++-
lib/towerops_web/plugs/rate_limit.ex | 2 +-
mix.exs | 2 +-
mix.lock | 2 +-
7 files changed, 17 insertions(+), 9 deletions(-)
create mode 100644 lib/towerops/rate_limit.ex
diff --git a/config/config.exs b/config/config.exs
index 248123ae..423eb48b 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -20,10 +20,6 @@ config :esbuild,
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}
]
-# Rate limiting with Hammer (ETS backend)
-config :hammer,
- backend: {Hammer.Backend.ETS, [expiry_ms: to_timeout(minute: 10), cleanup_interval_ms: to_timeout(minute: 1)]}
-
config :honeybadger,
api_key: "hbp_xe5xMnpLZ2XJsXQJujoEkgCmiqCfwa0uYA3Y",
environment_name: config_env(),
diff --git a/lib/towerops/application.ex b/lib/towerops/application.ex
index e98957be..ac158add 100644
--- a/lib/towerops/application.ex
+++ b/lib/towerops/application.ex
@@ -86,7 +86,7 @@ defmodule Towerops.Application do
# Encryption vault for sensitive data (passwords, API tokens)
Towerops.Vault,
# Rate limiting backend (ETS-based, per-node)
- {Hammer.Backend.ETS, [expiry_ms: to_timeout(minute: 10), cleanup_interval_ms: to_timeout(minute: 1)]},
+ {Towerops.RateLimit, [clean_period: to_timeout(minute: 1)]},
{Oban, Application.fetch_env!(:towerops, Oban)},
{DNSCluster, query: Application.get_env(:towerops, :dns_cluster_query) || :ignore},
pubsub_spec(),
diff --git a/lib/towerops/rate_limit.ex b/lib/towerops/rate_limit.ex
new file mode 100644
index 00000000..a5fba388
--- /dev/null
+++ b/lib/towerops/rate_limit.ex
@@ -0,0 +1,10 @@
+defmodule Towerops.RateLimit do
+ @moduledoc """
+ Rate limiting module using Hammer with ETS backend.
+
+ Provides in-memory rate limiting for auth and API endpoints.
+ Buckets are automatically cleaned up based on the configured clean_period.
+ """
+
+ use Hammer, backend: :ets
+end
diff --git a/lib/towerops_web/live/admin/monitoring_live.html.heex b/lib/towerops_web/live/admin/monitoring_live.html.heex
index 617484a2..733a283e 100644
--- a/lib/towerops_web/live/admin/monitoring_live.html.heex
+++ b/lib/towerops_web/live/admin/monitoring_live.html.heex
@@ -317,7 +317,9 @@
- {ToweropsWeb.TimeHelpers.format_time_ago(job.completed_at || job.cancelled_at || job.discarded_at || job.inserted_at)}
+ {ToweropsWeb.TimeHelpers.format_time_ago(
+ job.completed_at || job.cancelled_at || job.discarded_at || job.inserted_at
+ )}
diff --git a/lib/towerops_web/plugs/rate_limit.ex b/lib/towerops_web/plugs/rate_limit.ex
index 6fc411ab..0844db82 100644
--- a/lib/towerops_web/plugs/rate_limit.ex
+++ b/lib/towerops_web/plugs/rate_limit.ex
@@ -51,7 +51,7 @@ defmodule ToweropsWeb.Plugs.RateLimit do
{limit, period} = limits_for(type)
- case Hammer.check_rate(bucket_key, period, limit) do
+ case Towerops.RateLimit.hit(bucket_key, period, limit) do
{:allow, _count} ->
conn
diff --git a/mix.exs b/mix.exs
index 152f1987..327c1260 100644
--- a/mix.exs
+++ b/mix.exs
@@ -86,7 +86,7 @@ defmodule Towerops.MixProject do
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.14.1", only: [:dev, :test], runtime: false},
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
- {:hammer, "~> 6.2"},
+ {:hammer, "~> 7.2.0"},
{:cloak_ecto, "~> 1.3"},
{:logger_file_backend, "~> 0.0.13", only: :dev}
]
diff --git a/mix.lock b/mix.lock
index 18fcad60..fe3150cb 100644
--- a/mix.lock
+++ b/mix.lock
@@ -27,7 +27,7 @@
"gen_smtp": {:hex, :gen_smtp, "1.3.0", "62c3d91f0dcf6ce9db71bcb6881d7ad0d1d834c7f38c13fa8e952f4104a8442e", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0b73fbf069864ecbce02fe653b16d3f35fd889d0fdd4e14527675565c39d84e6"},
"gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"},
"hackney": {:hex, :hackney, "1.25.0", "390e9b83f31e5b325b9f43b76e1a785cbdb69b5b6cd4e079aa67835ded046867", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "7209bfd75fd1f42467211ff8f59ea74d6f2a9e81cbcee95a56711ee79fd6b1d4"},
- "hammer": {:hex, :hammer, "6.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"},
+ "hammer": {:hex, :hammer, "7.2.0", "73113eca87f0fd20a6d3679c1182e8c4c1778266f61de4e9dc8c589dee156c30", [:mix], [], "hexpm", "c50fa865ddfe7b3d4f8a6941f56940679e02a9a1465b00668a95d140b101d828"},
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
"honeybadger": {:hex, :honeybadger, "0.24.1", "13ffe56b4d148649c8fbb0e091fefecc5d8e8eb7ade684b6900085a947d741d5", [:mix], [{:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:hackney, "~> 1.1", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.0.0 and < 2.0.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:process_tree, "~> 0.2.1", [hex: :process_tree, repo: "hexpm", optional: false]}, {:req, "~> 0.5.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0c97d5a82c42298b9935dbc0a7e3c14372c8f55257f603828258ef9f7e0da892"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},