Public Access
feat(auth): rate limit registration, atomic login limit
10 failed registrations per IP block it for 15 minutes (429 TOO_MANY_ATTEMPTS with Retry-After). Login and registration now take an attempt before the check, so a burst of parallel requests cannot pass the limit.
This commit is contained in:
1 parent
c63b7e6aaa
commit
3a48ce0ff1
17 files changed
+445
-83
No files matched your search
@@ -5,8 +5,18 @@ import auth_service.shared.test.AbstractUnitTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class LoginAttemptServiceTest extends AbstractUnitTest {
|
||||
|
||||
@@ -20,33 +30,91 @@ class LoginAttemptServiceTest extends AbstractUnitTest {
|
||||
@Test
|
||||
void blocksEmailAfterFiveFailures() {
|
||||
for (int i = 0; i < LoginAttemptService.MAX_FAILURES_PER_EMAIL; i++) {
|
||||
attempts.checkAllowed("a@b.de", "1.1.1." + i);
|
||||
attempts.recordFailure("a@b.de", "1.1.1." + i);
|
||||
attempts.acquire("a@b.de", "1.1.1." + i);
|
||||
}
|
||||
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> attempts.checkAllowed("a@b.de", "9.9.9.9"));
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> attempts.checkAllowed("A@B.DE", "9.9.9.9"));
|
||||
assertDoesNotThrow(() -> attempts.checkAllowed("other@b.de", "9.9.9.9"));
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> attempts.acquire("a@b.de", "9.9.9.9"));
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> attempts.acquire("A@B.DE", "9.9.9.9"));
|
||||
assertDoesNotThrow(() -> attempts.acquire("other@b.de", "9.9.9.9"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blocksIpAfterTwentyFailures() {
|
||||
for (int i = 0; i < LoginAttemptService.MAX_FAILURES_PER_IP; i++) {
|
||||
attempts.recordFailure("user" + i + "@b.de", "1.2.3.4");
|
||||
attempts.acquire("user" + i + "@b.de", "1.2.3.4");
|
||||
}
|
||||
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> attempts.checkAllowed("new@b.de", "1.2.3.4"));
|
||||
assertDoesNotThrow(() -> attempts.checkAllowed("new@b.de", "5.6.7.8"));
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> attempts.acquire("new@b.de", "1.2.3.4"));
|
||||
assertDoesNotThrow(() -> attempts.acquire("new@b.de", "5.6.7.8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blockedIpDoesNotUseUpAnEmailAttempt() {
|
||||
for (int i = 0; i < LoginAttemptService.MAX_FAILURES_PER_IP; i++) {
|
||||
attempts.acquire("user" + i + "@b.de", "1.2.3.4");
|
||||
}
|
||||
for (int i = 0; i < LoginAttemptService.MAX_FAILURES_PER_EMAIL; i++) {
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> attempts.acquire("victim@b.de", "1.2.3.4"));
|
||||
}
|
||||
|
||||
assertDoesNotThrow(() -> attempts.acquire("victim@b.de", "5.6.7.8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void successResetsEmailCounter() {
|
||||
for (int i = 0; i < LoginAttemptService.MAX_FAILURES_PER_EMAIL - 1; i++) {
|
||||
attempts.recordFailure("a@b.de", "1.1.1.1");
|
||||
attempts.acquire("a@b.de", "1.1.1.1");
|
||||
}
|
||||
attempts.recordSuccess("a@b.de");
|
||||
attempts.recordFailure("a@b.de", "1.1.1.1");
|
||||
attempts.acquire("a@b.de", "1.1.1.1");
|
||||
attempts.recordSuccess("a@b.de", "1.1.1.1");
|
||||
attempts.acquire("a@b.de", "1.1.1.1");
|
||||
|
||||
assertDoesNotThrow(() -> attempts.checkAllowed("a@b.de", "1.1.1.1"));
|
||||
assertDoesNotThrow(() -> attempts.acquire("a@b.de", "1.1.1.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void successDoesNotUseUpAnIpAttempt() {
|
||||
for (int i = 0; i < LoginAttemptService.MAX_FAILURES_PER_IP * 2; i++) {
|
||||
attempts.acquire("user" + i + "@b.de", "1.2.3.4");
|
||||
attempts.recordSuccess("user" + i + "@b.de", "1.2.3.4");
|
||||
}
|
||||
|
||||
assertDoesNotThrow(() -> attempts.acquire("new@b.de", "1.2.3.4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentBurstCannotExceedEmailLimit() throws Exception {
|
||||
int requests = 50;
|
||||
CyclicBarrier allChecked = new CyclicBarrier(requests);
|
||||
AtomicInteger passed = new AtomicInteger();
|
||||
ExecutorService pool = Executors.newFixedThreadPool(requests);
|
||||
List<Future<?>> results = new ArrayList<>();
|
||||
|
||||
try {
|
||||
for (int i = 0; i < requests; i++) {
|
||||
String ip = "10.0.0." + i;
|
||||
results.add(pool.submit(() -> {
|
||||
boolean allowed = true;
|
||||
try {
|
||||
attempts.acquire("a@b.de", ip);
|
||||
} catch (TooManyLoginAttemptsException e) {
|
||||
allowed = false;
|
||||
}
|
||||
allChecked.await(5, TimeUnit.SECONDS);
|
||||
if (allowed) {
|
||||
passed.incrementAndGet();
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
for (Future<?> result : results) {
|
||||
result.get(10, TimeUnit.SECONDS);
|
||||
}
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
|
||||
assertTrue(passed.get() <= LoginAttemptService.MAX_FAILURES_PER_EMAIL,
|
||||
"attempts that passed the limit: " + passed.get());
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,8 @@ class LoginServiceTest extends AbstractUnitTest {
|
||||
|
||||
// Assert
|
||||
assertEquals("mock-token", result);
|
||||
verify(loginAttemptService).recordSuccess("test@example.com");
|
||||
verify(loginAttemptService).acquire("test@example.com", IP);
|
||||
verify(loginAttemptService).recordSuccess("test@example.com", IP);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -88,7 +89,8 @@ class LoginServiceTest extends AbstractUnitTest {
|
||||
assertThrows(InvalidCredentialsException.class, () -> loginService.login(user, IP));
|
||||
// bcrypt still runs, so the response time does not reveal that the email is unknown
|
||||
verify(passwordEncoder).matches("password", "dummy-hash");
|
||||
verify(loginAttemptService).recordFailure("test@example.com", IP);
|
||||
verify(loginAttemptService).acquire("test@example.com", IP);
|
||||
verify(loginAttemptService, never()).recordSuccess(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -104,7 +106,8 @@ class LoginServiceTest extends AbstractUnitTest {
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(InvalidCredentialsException.class, () -> loginService.login(user, IP));
|
||||
verify(loginAttemptService).recordFailure("test@example.com", IP);
|
||||
verify(loginAttemptService).acquire("test@example.com", IP);
|
||||
verify(loginAttemptService, never()).recordSuccess(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,7 +130,7 @@ class LoginServiceTest extends AbstractUnitTest {
|
||||
@Test
|
||||
void login_TooManyAttempts() {
|
||||
// Arrange
|
||||
doThrow(new TooManyLoginAttemptsException(900)).when(loginAttemptService).checkAllowed("test@example.com", IP);
|
||||
doThrow(new TooManyLoginAttemptsException(900)).when(loginAttemptService).acquire("test@example.com", IP);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(TooManyLoginAttemptsException.class, () -> loginService.login(user, IP));
|
||||
|
||||
+41
-7
@@ -4,6 +4,7 @@ import auth_service.configuration.security.JwtAuthenticationFilter;
|
||||
import auth_service.configuration.security.JwtService;
|
||||
import auth_service.modules.registration.api.dto.request.RegistrationRequest;
|
||||
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
||||
import auth_service.modules.registration.exception.TooManyRegistrationAttemptsException;
|
||||
import auth_service.modules.registration.model.Registration;
|
||||
import auth_service.modules.registration.service.RegistrationService;
|
||||
import auth_service.shared.test.AbstractWebMvcTest;
|
||||
@@ -21,12 +22,14 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -67,7 +70,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
long expectedUserId = 42L;
|
||||
|
||||
// Mock the service call, ignoring the exact static mapping output
|
||||
when(registrationService.register(any(Registration.class))).thenReturn(expectedUserId);
|
||||
when(registrationService.register(any(Registration.class), any())).thenReturn(expectedUserId);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
@@ -87,7 +90,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
.steamId("76561198000000001")
|
||||
.build();
|
||||
|
||||
when(registrationService.register(any(Registration.class)))
|
||||
when(registrationService.register(any(Registration.class), any()))
|
||||
.thenThrow(new EmailAlreadyInUseException("The provided email address is already in use."));
|
||||
|
||||
// Act & Assert
|
||||
@@ -108,7 +111,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
.inviteCode("code")
|
||||
.steamId("76561198000000001")
|
||||
.build();
|
||||
when(registrationService.register(any(Registration.class))).thenReturn(42L);
|
||||
when(registrationService.register(any(Registration.class), any())).thenReturn(42L);
|
||||
|
||||
// Act
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
@@ -118,7 +121,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
|
||||
// Assert
|
||||
var captor = ArgumentCaptor.forClass(Registration.class);
|
||||
verify(registrationService).register(captor.capture());
|
||||
verify(registrationService).register(captor.capture(), any());
|
||||
assertEquals(76561198000000001L, captor.getValue().getSteamId());
|
||||
}
|
||||
|
||||
@@ -160,7 +163,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
.password("SuperSecret123!")
|
||||
.inviteCode("code")
|
||||
.build();
|
||||
when(registrationService.register(any(Registration.class)))
|
||||
when(registrationService.register(any(Registration.class), any()))
|
||||
.thenThrow(new DataIntegrityViolationException("users_email_key"));
|
||||
|
||||
// Act & Assert: the user module's 409 mapping must not apply to registration
|
||||
@@ -183,7 +186,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
@Test
|
||||
void register_PassesDisplayName() throws Exception {
|
||||
// Arrange
|
||||
when(registrationService.register(any(Registration.class))).thenReturn(42L);
|
||||
when(registrationService.register(any(Registration.class), any())).thenReturn(42L);
|
||||
|
||||
// Act
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
@@ -194,7 +197,38 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
|
||||
// Assert
|
||||
var captor = ArgumentCaptor.forClass(Registration.class);
|
||||
verify(registrationService).register(captor.capture());
|
||||
verify(registrationService).register(captor.capture(), any());
|
||||
assertEquals("Lobo", captor.getValue().getDisplayName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_UsesLastForwardedForHopAsClientIp() throws Exception {
|
||||
// Arrange
|
||||
when(registrationService.register(any(Registration.class), eq("203.0.113.7"))).thenReturn(42L);
|
||||
|
||||
// Act & Assert
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.header("X-Forwarded-For", "10.0.0.1, 203.0.113.7")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code"}"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.userId").value(42L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_TooManyAttempts() throws Exception {
|
||||
// Arrange
|
||||
when(registrationService.register(any(Registration.class), any()))
|
||||
.thenThrow(new TooManyRegistrationAttemptsException(900));
|
||||
|
||||
// Act & Assert
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code"}"""))
|
||||
.andExpect(status().isTooManyRequests())
|
||||
.andExpect(header().string("Retry-After", "900"))
|
||||
.andExpect(jsonPath("$.code").value("TOO_MANY_ATTEMPTS"));
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package auth_service.modules.registration.service;
|
||||
|
||||
import auth_service.modules.registration.exception.TooManyRegistrationAttemptsException;
|
||||
import auth_service.shared.test.AbstractUnitTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RegistrationAttemptServiceTest extends AbstractUnitTest {
|
||||
|
||||
private RegistrationAttemptService attempts;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
attempts = new RegistrationAttemptService();
|
||||
}
|
||||
|
||||
@Test
|
||||
void blocksIpAfterTenFailures() {
|
||||
for (int i = 0; i < RegistrationAttemptService.MAX_FAILURES_PER_IP; i++) {
|
||||
attempts.acquire("1.2.3.4");
|
||||
}
|
||||
|
||||
TooManyRegistrationAttemptsException blocked =
|
||||
assertThrows(TooManyRegistrationAttemptsException.class, () -> attempts.acquire("1.2.3.4"));
|
||||
assertEquals(RegistrationAttemptService.LOCK_DURATION.toSeconds(), blocked.getRetryAfterSeconds());
|
||||
assertDoesNotThrow(() -> attempts.acquire("5.6.7.8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void successDoesNotUseUpAnAttempt() {
|
||||
for (int i = 0; i < RegistrationAttemptService.MAX_FAILURES_PER_IP * 2; i++) {
|
||||
attempts.acquire("1.2.3.4");
|
||||
attempts.release("1.2.3.4");
|
||||
}
|
||||
|
||||
assertDoesNotThrow(() -> attempts.acquire("1.2.3.4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blockedAttemptIsNotCounted() {
|
||||
for (int i = 0; i < RegistrationAttemptService.MAX_FAILURES_PER_IP; i++) {
|
||||
attempts.acquire("1.2.3.4");
|
||||
}
|
||||
assertThrows(TooManyRegistrationAttemptsException.class, () -> attempts.acquire("1.2.3.4"));
|
||||
attempts.release("1.2.3.4");
|
||||
|
||||
assertDoesNotThrow(() -> attempts.acquire("1.2.3.4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentBurstCannotExceedLimit() throws Exception {
|
||||
int requests = 50;
|
||||
CyclicBarrier allChecked = new CyclicBarrier(requests);
|
||||
AtomicInteger passed = new AtomicInteger();
|
||||
ExecutorService pool = Executors.newFixedThreadPool(requests);
|
||||
List<Future<?>> results = new ArrayList<>();
|
||||
|
||||
try {
|
||||
for (int i = 0; i < requests; i++) {
|
||||
results.add(pool.submit(() -> {
|
||||
boolean allowed = true;
|
||||
try {
|
||||
attempts.acquire("1.2.3.4");
|
||||
} catch (TooManyRegistrationAttemptsException e) {
|
||||
allowed = false;
|
||||
}
|
||||
allChecked.await(5, TimeUnit.SECONDS);
|
||||
if (allowed) {
|
||||
passed.incrementAndGet();
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
for (Future<?> result : results) {
|
||||
result.get(10, TimeUnit.SECONDS);
|
||||
}
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
|
||||
assertTrue(passed.get() <= RegistrationAttemptService.MAX_FAILURES_PER_IP,
|
||||
"attempts that passed the limit: " + passed.get());
|
||||
}
|
||||
}
|
||||
+29
-6
@@ -4,6 +4,7 @@ import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
||||
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
||||
import auth_service.modules.registration.exception.DisplayNameAlreadyRegisteredException;
|
||||
import auth_service.modules.registration.exception.SteamIdAlreadyRegisteredException;
|
||||
import auth_service.modules.registration.exception.TooManyRegistrationAttemptsException;
|
||||
import auth_service.modules.registration.model.Registration;
|
||||
import auth_service.modules.registration.persistence.RegisteredUserRepository;
|
||||
import auth_service.shared.test.AbstractUnitTest;
|
||||
@@ -23,6 +24,8 @@ import static org.mockito.Mockito.*;
|
||||
*/
|
||||
class RegistrationServiceTest extends AbstractUnitTest {
|
||||
|
||||
private static final String IP = "203.0.113.7";
|
||||
|
||||
@Mock
|
||||
private CodeValidationService codeValidationService;
|
||||
|
||||
@@ -32,6 +35,9 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
@Mock
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Mock
|
||||
private RegistrationAttemptService registrationAttemptService;
|
||||
|
||||
@InjectMocks
|
||||
private RegistrationService registrationService;
|
||||
|
||||
@@ -58,13 +64,15 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
when(registeredUserRepository.save(any(Registration.class))).thenReturn(expectedUserId);
|
||||
|
||||
// Act
|
||||
long result = registrationService.register(validRegistration);
|
||||
long result = registrationService.register(validRegistration, IP);
|
||||
|
||||
// Assert
|
||||
assertEquals(expectedUserId, result);
|
||||
verify(passwordEncoder).encode("SuperSecret123!");
|
||||
verify(registeredUserRepository).save(validRegistration);
|
||||
assertEquals("hashedPasswordXYZ", validRegistration.getEncryptedPassword());
|
||||
verify(registrationAttemptService).acquire(IP);
|
||||
verify(registrationAttemptService).release(IP);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,10 +81,11 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(false);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(InvalidInvitationCodeException.class, () -> registrationService.register(validRegistration));
|
||||
assertThrows(InvalidInvitationCodeException.class, () -> registrationService.register(validRegistration, IP));
|
||||
|
||||
verify(registeredUserRepository, never()).existsByEmail(anyString());
|
||||
verify(registeredUserRepository, never()).save(any());
|
||||
verify(registrationAttemptService, never()).release(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,10 +95,11 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(true);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(EmailAlreadyInUseException.class, () -> registrationService.register(validRegistration));
|
||||
assertThrows(EmailAlreadyInUseException.class, () -> registrationService.register(validRegistration, IP));
|
||||
|
||||
verify(registeredUserRepository, never()).existsBySteamId(anyLong());
|
||||
verify(registeredUserRepository, never()).save(any());
|
||||
verify(registrationAttemptService, never()).release(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,9 +110,10 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
when(registeredUserRepository.existsBySteamId(validRegistration.getSteamId())).thenReturn(true);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(SteamIdAlreadyRegisteredException.class, () -> registrationService.register(validRegistration));
|
||||
assertThrows(SteamIdAlreadyRegisteredException.class, () -> registrationService.register(validRegistration, IP));
|
||||
|
||||
verify(registeredUserRepository, never()).save(any());
|
||||
verify(registrationAttemptService, never()).release(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,7 +126,7 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
when(registeredUserRepository.save(any(Registration.class))).thenReturn(42L);
|
||||
|
||||
// Act
|
||||
long result = registrationService.register(validRegistration);
|
||||
long result = registrationService.register(validRegistration, IP);
|
||||
|
||||
// Assert
|
||||
assertEquals(42L, result);
|
||||
@@ -132,7 +143,19 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
when(registeredUserRepository.existsByDisplayName("Lobo")).thenReturn(true);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(DisplayNameAlreadyRegisteredException.class, () -> registrationService.register(validRegistration));
|
||||
assertThrows(DisplayNameAlreadyRegisteredException.class, () -> registrationService.register(validRegistration, IP));
|
||||
verify(registeredUserRepository, never()).save(any());
|
||||
verify(registrationAttemptService, never()).release(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_BlockedIp_ChecksNothingElse() {
|
||||
// Arrange
|
||||
doThrow(new TooManyRegistrationAttemptsException(900)).when(registrationAttemptService).acquire(IP);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(TooManyRegistrationAttemptsException.class, () -> registrationService.register(validRegistration, IP));
|
||||
verifyNoInteractions(codeValidationService, registeredUserRepository);
|
||||
verify(registrationAttemptService, never()).release(anyString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package auth_service.shared.api;
|
||||
|
||||
import auth_service.shared.test.AbstractUnitTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class ClientIpTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void commaOnlyForwardedForFallsBackToRemoteAddr() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRemoteAddr("198.51.100.9");
|
||||
request.addHeader("X-Forwarded-For", ",");
|
||||
|
||||
assertEquals("198.51.100.9", ClientIp.of(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankLastHopFallsBackToRemoteAddr() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRemoteAddr("198.51.100.9");
|
||||
request.addHeader("X-Forwarded-For", "10.0.0.1, ");
|
||||
|
||||
assertEquals("198.51.100.9", ClientIp.of(request));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user