This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@spec allow_login_attempt?(String.t(), non_neg_integer()) :: boolean() | |
def allow_login_attempt?(email, limit \\ @login_request_limit) do |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def allow_login_attempt?(email, limit \\ @login_request_limit) | |
when is_binary(email) and is_integer(limit) and limit >= 0 do |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ...code omitted | |
require Logger | |
# ...code omitted | |
def allow_login_attempt?(email, limit \\ @login_request_limit) do | |
case ExRated.check_rate("login_attempt:#{email}", @time_window, limit) do | |
{:ok, _count} -> true | |
{:error, _limit} -> | |
Logger.warning("Login attempt limit exceeded for email: #{email}") | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
doctest RateLimiter | |
test "allow_login_attempt?/2 returns true when login attempts do not exceed a limit of #{@limit}" do | |
assert RateLimiter.allow_login_attempt?(@test_email, @limit) == true | |
assert RateLimiter.allow_login_attempt?(@test_email, @limit) == true | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@test_email "test@example.com" | |
@limit 2 | |
setup do | |
# Empty the login attempt bucket for @test_email before each test | |
ExRated.delete_bucket("login_attempt:#{@test_email}") | |
:ok | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ...code omitted | |
import ExUnit.CaptureLog | |
# ...code omitted | |
test "allow_login_attempt?/2 returns false with a warning log when over the login attempt limit" do | |
assert RateLimiter.allow_login_attempt?(@test_email, @limit) == true | |
assert RateLimiter.allow_login_attempt?(@test_email, @limit) == true | |
log = capture_log(fn -> | |
assert RateLimiter.allow_login_attempt?(@test_email, @limit) == false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule RateLimiterTest do | |
use ExUnit.Case | |
import ExUnit.CaptureLog | |
@test_email "test@example.com" | |
@limit 2 | |
# ...code omitted |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@doc """ | |
iex> RateLimiter.allow_login_attempt?("doctest@example.com", 1) | |
true | |
iex> RateLimiter.allow_login_attempt?("doctest@example.com", 0) | |
false | |
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ...code omitted | |
@login_request_limit 4 # login request limit per time window | |
@time_window 600_000 # 10 minutes in milliseconds | |
# ...doc omitted | |
def allow_login_attempt?(email, limit \\ @login_request_limit) do | |
case ExRated.check_rate("login_attempt:#{email}", @time_window, limit) do | |
{:ok, _count} -> true | |
{:error, _limit} -> false | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule RateLimiter do | |
# ...code omitted | |
@doc """ | |
Check the login attempt rate limit for a given email address. | |
iex> RateLimiter.allow_login_attempt?("doctest@example.com", 1) | |
true | |
iex> RateLimiter.allow_login_attempt?("doctest@example.com", 0) |
NewerOlder