feat(security): CSRF protection, session revocation, login rate limiting (#524) #617

Merged
marcel merged 26 commits from feat/issue-524-csrf-session-rate-limit into main 2026-05-19 09:23:03 +02:00
2 changed files with 8 additions and 5 deletions
Showing only changes of commit 2f981ef69d - Show all commits

View File

@@ -5,8 +5,9 @@ import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.exception.ErrorCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class LoginRateLimiterTest { class LoginRateLimiterTest {
@@ -37,7 +38,7 @@ class LoginRateLimiterTest {
assertThatThrownBy(() -> rateLimiter.checkAndConsume("1.2.3.4", "user@example.com")) assertThatThrownBy(() -> rateLimiter.checkAndConsume("1.2.3.4", "user@example.com"))
.isInstanceOf(DomainException.class) .isInstanceOf(DomainException.class)
.satisfies(ex -> org.assertj.core.api.Assertions.assertThat(((DomainException) ex).getCode()) .satisfies(ex -> assertThat(((DomainException) ex).getCode())
.isEqualTo(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS)); .isEqualTo(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS));
} }
@@ -61,7 +62,7 @@ class LoginRateLimiterTest {
assertThatThrownBy(() -> rateLimiter.checkAndConsume("1.2.3.4", "attacker@example.com")) assertThatThrownBy(() -> rateLimiter.checkAndConsume("1.2.3.4", "attacker@example.com"))
.isInstanceOf(DomainException.class) .isInstanceOf(DomainException.class)
.satisfies(ex -> org.assertj.core.api.Assertions.assertThat(((DomainException) ex).getCode()) .satisfies(ex -> assertThat(((DomainException) ex).getCode())
.isEqualTo(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS)); .isEqualTo(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS));
} }
@@ -86,7 +87,7 @@ class LoginRateLimiterTest {
assertThatThrownBy(() -> rateLimiter.checkAndConsume("1.2.3.4", "user@example.com")) assertThatThrownBy(() -> rateLimiter.checkAndConsume("1.2.3.4", "user@example.com"))
.isInstanceOf(DomainException.class) .isInstanceOf(DomainException.class)
.satisfies(ex -> org.assertj.core.api.Assertions.assertThat(((DomainException) ex).getCode()) .satisfies(ex -> assertThat(((DomainException) ex).getCode())
.isEqualTo(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS)); .isEqualTo(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS));
} }

View File

@@ -20,6 +20,8 @@ import org.springframework.test.web.servlet.MockMvc;
import java.util.UUID; import java.util.UUID;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -178,7 +180,7 @@ class UserControllerTest {
.content("{\"currentPassword\":\"old\",\"newPassword\":\"new123!\"}")) .content("{\"currentPassword\":\"old\",\"newPassword\":\"new123!\"}"))
.andExpect(status().isNoContent()); .andExpect(status().isNoContent());
org.mockito.Mockito.verify(authService).revokeOtherSessions(any(), org.mockito.ArgumentMatchers.eq("user@example.com")); verify(authService).revokeOtherSessions(any(), eq("user@example.com"));
} }
@Test @Test