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"},