# `PhoenixKit.Users.RateLimiter`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.14.0/lib/phoenix_kit/users/rate_limiter.ex#L8)

Rate limiting for authentication endpoints to prevent brute-force attacks.

This module provides comprehensive rate limiting protection for:
- Login attempts (prevents password brute-forcing)
- Magic link generation (prevents token enumeration)
- Password reset requests (prevents mass reset attacks)
- User registration (prevents spam account creation)

## Configuration

Rate limits can be configured in your application config:

    # config/config.exs
    config :phoenix_kit, PhoenixKit.Users.RateLimiter,
      login_limit: 5,                    # Max login attempts per window
      login_window_ms: 60_000,           # 1 minute window
      magic_link_limit: 3,               # Max magic link requests per window
      magic_link_window_ms: 300_000,     # 5 minute window
      password_reset_limit: 3,           # Max password reset requests per window
      password_reset_window_ms: 300_000, # 5 minute window
      registration_limit: 3,             # Max registration attempts per window
      registration_window_ms: 3600_000,  # 1 hour window
      registration_ip_limit: 10,         # Max registrations per IP per window
      registration_ip_window_ms: 3600_000 # 1 hour window

## Security Features

- **Email-based rate limiting**: Prevents targeted attacks on specific accounts
- **IP-based rate limiting**: Prevents distributed attacks from single sources
- **Timing attack mitigation**: Consistent response times for valid/invalid emails
- **Exponential backoff**: Automatically enforced through time windows
- **Comprehensive logging**: All rate limit violations are logged for security monitoring

## Usage Examples

    # Check login rate limit
    case PhoenixKit.Users.RateLimiter.check_login_rate_limit(email, ip_address) do
      :ok -> proceed_with_login()
      {:error, :rate_limit_exceeded} -> show_rate_limit_error()
    end

    # Check magic link rate limit
    case PhoenixKit.Users.RateLimiter.check_magic_link_rate_limit(email) do
      :ok -> generate_magic_link()
      {:error, :rate_limit_exceeded} -> show_cooldown_message()
    end

## Production Recommendations

- Use Redis backend for distributed systems (hammer_backend_redis)
- Monitor rate limit violations for security threats
- Adjust limits based on your application's usage patterns
- Consider implementing CAPTCHA after multiple violations

# `check_access_request_rate_limit`

Checks if a logged-in account's mention access-request submissions are
within limit.

Keyed on the account: the request path is post-login, and the partial
unique index only bounds repeats for one resource. Without a per-account
limit a client can invent distinct uuids and fill the activity feed one
row at a time.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_access_request_rate_limit("0193a5e4-0000-7000-8000-000000000001")
    :ok

# `check_confirmation_resend_rate_limit`

Checks if confirmation-email resends are within rate limit.

The resend form is public and unauthenticated: each accepted request inserts
a token and sends mail, so without a limit it is both a targeted mail-flood
vector and — because only existing unconfirmed accounts do that work — a
measurable oracle for which addresses are registered but unconfirmed.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_confirmation_resend_rate_limit("user@example.com")
    :ok

# `check_login_rate_limit`

Checks if login attempts are within rate limit.

Returns `:ok` if the request is allowed, or `{:error, :rate_limit_exceeded}` if the limit is exceeded.

This function implements dual rate limiting:
- Per-email rate limiting (prevents targeted attacks on specific accounts)
- Per-IP rate limiting (prevents distributed brute-force attacks)

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_login_rate_limit("user@example.com", "192.168.1.1")
    :ok

    # After 5 failed attempts:
    iex> PhoenixKit.Users.RateLimiter.check_login_rate_limit("user@example.com", "192.168.1.1")
    {:error, :rate_limit_exceeded}

# `check_magic_link_rate_limit`

Checks if magic link generation is within rate limit.

Returns `:ok` if the request is allowed, or `{:error, :rate_limit_exceeded}` if the limit is exceeded.

Magic links have stricter rate limits to prevent token enumeration attacks.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_magic_link_rate_limit("user@example.com")
    :ok

    # After 3 requests in 5 minutes:
    iex> PhoenixKit.Users.RateLimiter.check_magic_link_rate_limit("user@example.com")
    {:error, :rate_limit_exceeded}

# `check_password_reset_rate_limit`

Checks if password reset requests are within rate limit.

Returns `:ok` if the request is allowed, or `{:error, :rate_limit_exceeded}` if the limit is exceeded.

Password reset requests have moderate rate limits to prevent mass reset attacks
while still allowing legitimate users to recover their accounts.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_password_reset_rate_limit("user@example.com")
    :ok

    # After 3 requests in 5 minutes:
    iex> PhoenixKit.Users.RateLimiter.check_password_reset_rate_limit("user@example.com")
    {:error, :rate_limit_exceeded}

# `check_qr_login_rate_limit`

Checks if QR device-handoff login request creation is within rate limit.

Returns `:ok` if the request is allowed, or `{:error, :rate_limit_exceeded}` if the limit is
exceeded. IP-only (the desktop page is pre-auth, so there's no email to key on) — this guards
against an anonymous visitor repeatedly minting `keyfob` requests (each one a live ETS entry)
by hammering the public `/users/qr-login` page.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_qr_login_rate_limit("192.168.1.1")
    :ok

# `check_referral_redemption_rate_limit`

Checks if a logged-in account's referral-code redemption attempts are within
limit.

Keyed on the account, not the IP: the invite-only screen sits *behind* login,
so an attacker who can mint accounts gets a fresh IP-keyed bucket with every
one. Without a per-account limit the screen is the same guessing oracle the
registration form was, only cheaper to farm.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_referral_redemption_rate_limit("0193a5e4-0000-7000-8000-000000000001")
    :ok

# `check_referral_validation_rate_limit`

Checks if referral code validation attempts are within rate limit.

Returns `:ok` if the attempt is allowed, or `{:error, :rate_limit_exceeded}` if
the limit is exceeded. IP-only: the registration form is pre-auth, so there is
no account to key on.

This is what makes a referral code hard to guess. `Auth.register_user/2`'s
limiter sits behind referral validation, so a wrong code short-circuits before
reaching it — leaving code checking unthrottled unless it is limited here.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_referral_validation_rate_limit("192.168.1.1")
    :ok

# `check_registration_rate_limit`

Checks if registration attempts are within rate limit.

Returns `:ok` if the request is allowed, or `{:error, :rate_limit_exceeded}` if the limit is exceeded.

Registration has dual rate limiting:
- Per-email rate limiting (prevents spam account creation with same email)
- Per-IP rate limiting (prevents mass account creation from single source)

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_registration_rate_limit("user@example.com", "192.168.1.1")
    :ok

    # After limit exceeded:
    iex> PhoenixKit.Users.RateLimiter.check_registration_rate_limit("user@example.com", "192.168.1.1")
    {:error, :rate_limit_exceeded}

# `check_upload_rate_limit`

Checks whether file uploads are within rate limit, keyed on the uploading
account. `POST /api/upload` requires authentication (see
`PhoenixKitWeb.UploadController.resolve_upload_user/2`), so there is always an
account to key on; this bounds storage-exhaustion and Oban-queue abuse by an
authenticated user.

## Examples

    iex> PhoenixKit.Users.RateLimiter.check_upload_rate_limit(user_uuid)
    :ok

# `get_config`

# `get_remaining_attempts`

Gets the remaining attempts for a specific action and identifier.

Returns the number of attempts remaining before rate limit is exceeded.

For login and registration actions, returns the email-based limit.
For magic_link and password_reset, returns the limit for the email.

Note: With Hammer 7.x, this uses the get/2 function to retrieve the current count.

## Examples

    iex> PhoenixKit.Users.RateLimiter.get_remaining_attempts(:login, "user@example.com")
    5

    iex> PhoenixKit.Users.RateLimiter.get_remaining_attempts(:magic_link, "user@example.com")
    3

# `reset_rate_limit`

> This function is deprecated. Hammer 7.x removed delete_buckets. Rate limits expire after their time window..

Resets rate limit for a specific action and identifier.

**DEPRECATED:** Hammer 7.x removed `delete_buckets` with no replacement.
This function now returns an error as Backend.set/3 requires positive integers (cannot set to 0).

Rate limits will naturally expire after their configured window period.

## Migration

- **For testing**: Use `Application.put_env` to disable rate limiting
- **For admin intervention**: Wait for the time window to expire
- **For immediate reset**: Restart the application (clears ETS tables)

See: https://hexdocs.pm/hammer/upgrade-v7.html

## Examples

    iex> PhoenixKit.Users.RateLimiter.reset_rate_limit(:login, "email:user@example.com")
    {:error, :not_supported}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
