mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-23 10:05:10 -07:00
[Security] Add per-address rate limiting for auth endpoints
Introduce a thread-safe RateLimiter that tracks attempts per key (IP address) within a sliding time window, and wire it into the authentication endpoints: - Login: failed login attempts from an address are counted; once the configured maximum is exceeded within the window, further logins from that address are rejected with RespTooManyRequests. A successful login clears the failed attempts for that address. - Registration: implement the previously stubbed tooManyRegistrationAttempts, limiting how many accounts can be created per address per window. - Forgot-password: throttle both the email-request and the email-challenge paths per address. New [security] settings with defaults: max_login_attempts_per_ip=5 / login_attempt_window_seconds=900 max_registrations_per_ip=2 / registration_window_seconds=3600 max_forgot_password_requests_per_ip=3 / forgot_password_window_seconds=3600 Adds unit tests for the RateLimiter (window limit, over-limit blocking, clearing, per-key independence). Took 3 minutes
This commit is contained in:
parent
1ed9823b56
commit
b2f63255f0
11 changed files with 238 additions and 3 deletions
|
|
@ -7,6 +7,7 @@ add_test(NAME expression_test COMMAND expression_test)
|
|||
add_test(NAME clamped_arithmetic_test COMMAND clamped_arithmetic_test)
|
||||
add_test(NAME test_age_formatting COMMAND test_age_formatting)
|
||||
add_test(NAME password_hash_test COMMAND password_hash_test)
|
||||
add_test(NAME rate_limiter_test COMMAND rate_limiter_test)
|
||||
add_test(NAME server_card_counter_test COMMAND server_card_counter_test)
|
||||
add_test(NAME server_counter_test COMMAND server_counter_test)
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ add_executable(expression_test expression_test.cpp)
|
|||
add_executable(clamped_arithmetic_test clamped_arithmetic_test.cpp)
|
||||
add_executable(test_age_formatting test_age_formatting.cpp)
|
||||
add_executable(password_hash_test password_hash_test.cpp)
|
||||
add_executable(rate_limiter_test ../servatrice/src/ratelimiter.cpp rate_limiter_test.cpp)
|
||||
add_executable(deck_hash_performance_test deck_hash_performance_test.cpp)
|
||||
add_executable(server_card_counter_test server_card_counter_test.cpp)
|
||||
add_executable(server_counter_test server_counter_test.cpp)
|
||||
|
|
@ -54,6 +56,7 @@ if(NOT GTEST_FOUND)
|
|||
add_dependencies(clamped_arithmetic_test gtest)
|
||||
add_dependencies(test_age_formatting gtest)
|
||||
add_dependencies(password_hash_test gtest)
|
||||
add_dependencies(rate_limiter_test gtest)
|
||||
add_dependencies(deck_hash_performance_test gtest)
|
||||
add_dependencies(server_card_counter_test gtest)
|
||||
add_dependencies(server_counter_test gtest)
|
||||
|
|
@ -71,6 +74,8 @@ target_link_libraries(
|
|||
target_link_libraries(
|
||||
password_hash_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||
)
|
||||
target_include_directories(rate_limiter_test PRIVATE "${CMAKE_SOURCE_DIR}/servatrice/src")
|
||||
target_link_libraries(rate_limiter_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
|
||||
target_link_libraries(
|
||||
deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||
${TEST_QT_MODULES}
|
||||
|
|
|
|||
49
tests/rate_limiter_test.cpp
Normal file
49
tests/rate_limiter_test.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include "ratelimiter.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
TEST(RateLimiterTest, AllowsAttemptsWithinLimit)
|
||||
{
|
||||
RateLimiter limiter;
|
||||
ASSERT_FALSE(limiter.recordAttempt("ip", 3, 60));
|
||||
ASSERT_FALSE(limiter.recordAttempt("ip", 3, 60));
|
||||
ASSERT_FALSE(limiter.recordAttempt("ip", 3, 60));
|
||||
ASSERT_FALSE(limiter.isBlocked("ip", 3, 60));
|
||||
}
|
||||
|
||||
TEST(RateLimiterTest, BlocksAttemptsOverLimit)
|
||||
{
|
||||
RateLimiter limiter;
|
||||
ASSERT_FALSE(limiter.recordAttempt("ip", 2, 60));
|
||||
ASSERT_FALSE(limiter.recordAttempt("ip", 2, 60));
|
||||
ASSERT_TRUE(limiter.recordAttempt("ip", 2, 60));
|
||||
ASSERT_TRUE(limiter.isBlocked("ip", 2, 60));
|
||||
}
|
||||
|
||||
TEST(RateLimiterTest, ClearAttempts)
|
||||
{
|
||||
RateLimiter limiter;
|
||||
ASSERT_TRUE(limiter.recordAttempt("ip", 0, 60));
|
||||
ASSERT_TRUE(limiter.isBlocked("ip", 0, 60));
|
||||
limiter.clearAttempts("ip");
|
||||
ASSERT_FALSE(limiter.isBlocked("ip", 0, 60));
|
||||
}
|
||||
|
||||
TEST(RateLimiterTest, KeysAreIndependent)
|
||||
{
|
||||
RateLimiter limiter;
|
||||
ASSERT_TRUE(limiter.recordAttempt("a", 0, 60));
|
||||
ASSERT_FALSE(limiter.isBlocked("b", 0, 60));
|
||||
ASSERT_TRUE(limiter.isBlocked("a", 0, 60));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue