README
AshRateLimiter
Welcome! This is an extension for the Ash framework which protects actions from abuse by enforcing rate limits.
Uses the excellent hammer to provide rate limiting functionality.
Installation
Add ash_rate_limiter
to your list of dependencies in mix.exs
:
def deps do
[
{:ash_rate_limiter, "~> 0.1.2"}
]
end
Quick Start
- Add a Hammer module: You need to create a Hammer module:
# lib/my_app/hammer.ex
defmodule MyApp.Hammer do
use Hammer, backend: :ets
end
- Add to your resource: Use the
rate_limit
DSL section in your Ash resource:
defmodule MyApp.Post do
use Ash.Resource,
domain: MyApp,
extensions: [AshRateLimiter]
rate_limit do
# Configure hammer backend
hammer MyApp.Hammer
# Limit create action to 10 requests per 5 minutes
action :create, limit: 10, per: :timer.minutes(5)
# Limit read action to 100 requests per minute
action :read, limit: 100, per: :timer.minutes(1)
end
# ... rest of your resource definition
end
- That's it! Your actions are now rate limited. When the limit is exceeded, an
AshRateLimiter.LimitExceeded
error will be raised.
Basic Usage
Simple Rate Limiting
rate_limit do
hammer MyApp.Hammer
action :create, limit: 5, per: :timer.minutes(1)
end
Per-User Rate Limiting
rate_limit do
hammer MyApp.Hammer
action :create,
limit: 10,
per: :timer.minutes(5),
key: fn changeset, context ->
"user:#{context.actor.id}:create"
end
end
Multiple Actions
rate_limit do
hammer MyApp.Hammer
action :create, limit: 10, per: :timer.minutes(5)
action :update, limit: 20, per: :timer.minutes(5)
action :read, limit: 100, per: :timer.minutes(1)
end
Advanced Usage
Manual Integration
For more control, you can add rate limiting directly to specific actions:
defmodule MyApp.Post do
use Ash.Resource, domain: MyApp
actions do
create :create do
change {AshRateLimiter.Change, limit: 10, per: :timer.minutes(5)}
end
read :read do
prepare {AshRateLimiter.Preparation, limit: 100, per: :timer.minutes(1)}
end
end
end
Or use the built-in helpers:
actions do
create :create do
change rate_limit(limit: 10, per: :timer.minutes(5))
end
end
Custom Key Functions
The key function determines how requests are grouped for rate limiting:
# Rate limit per IP address
key: fn _changeset, context ->
"ip:#{context[:ip_address]}"
end
# Rate limit per user and action
key: fn changeset, context ->
"user:#{context.actor.id}:action:#{changeset.action.name}"
end
# Use the built-in key function with options
key: {&AshRateLimiter.key_for_action/2, include_actor_attributes: [:role]}
Error Handling
When rate limits are exceeded, an AshRateLimiter.LimitExceeded
exception is raised:
case MyApp.create_post(attrs) do
{:ok, post} ->
# Success
{:ok, post}
{:error, %AshRateLimiter.LimitExceeded{} = error} ->
# Rate limit exceeded
{:error, "Too many requests, please try again later"}
{:error, other_error} ->
# Handle other errors
{:error, other_error}
end
In web applications, the exception includes Plug.Exception
behaviour for automatic HTTP 429 responses.