From 778402fec72704759b501481fdb03b89d78e267e Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 18 May 2026 22:30:50 +0200 Subject: [PATCH] test(auth): add integration-level CSRF rejection test; fix SessionRevocationPort wiring Integration test: - Adds post_without_csrf_token_returns_403_CSRF_TOKEN_MISSING to AuthSessionIntegrationTest, verifying CSRF is active end-to-end (not just in @WebMvcTest slices). SessionRevocationConfig (new): - Replaces fragile @ConditionalOnBean/@ConditionalOnMissingBean on @Service beans with a single @Configuration @Bean method that accepts JdbcIndexedSessionRepository as @Autowired(required=false). Spring resolves the optional parameter reliably after auto-configuration fires, choosing JdbcSessionRevocationAdapter when available and NoOpSessionRevocationAdapter otherwise. - JdbcSessionRevocationAdapter and NoOpSessionRevocationAdapter are now plain implementation classes (no @Service/@Conditional annotations). Addresses Sara Concern 2 from PR #617 review. Co-Authored-By: Claude Sonnet 4.6 --- .../auth/JdbcSessionRevocationAdapter.java | 4 ---- .../auth/NoOpSessionRevocationAdapter.java | 5 ----- .../auth/SessionRevocationConfig.java | 19 +++++++++++++++++++ .../auth/AuthSessionIntegrationTest.java | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/org/raddatz/familienarchiv/auth/SessionRevocationConfig.java diff --git a/backend/src/main/java/org/raddatz/familienarchiv/auth/JdbcSessionRevocationAdapter.java b/backend/src/main/java/org/raddatz/familienarchiv/auth/JdbcSessionRevocationAdapter.java index ba24a23a..e3c39a58 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/auth/JdbcSessionRevocationAdapter.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/auth/JdbcSessionRevocationAdapter.java @@ -1,12 +1,8 @@ package org.raddatz.familienarchiv.auth; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.session.jdbc.JdbcIndexedSessionRepository; -import org.springframework.stereotype.Service; -@Service -@ConditionalOnBean(JdbcIndexedSessionRepository.class) @RequiredArgsConstructor class JdbcSessionRevocationAdapter implements SessionRevocationPort { diff --git a/backend/src/main/java/org/raddatz/familienarchiv/auth/NoOpSessionRevocationAdapter.java b/backend/src/main/java/org/raddatz/familienarchiv/auth/NoOpSessionRevocationAdapter.java index bb3d3ede..cdb5ec63 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/auth/NoOpSessionRevocationAdapter.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/auth/NoOpSessionRevocationAdapter.java @@ -1,10 +1,5 @@ package org.raddatz.familienarchiv.auth; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.stereotype.Service; - -@Service -@ConditionalOnMissingBean(SessionRevocationPort.class) class NoOpSessionRevocationAdapter implements SessionRevocationPort { @Override diff --git a/backend/src/main/java/org/raddatz/familienarchiv/auth/SessionRevocationConfig.java b/backend/src/main/java/org/raddatz/familienarchiv/auth/SessionRevocationConfig.java new file mode 100644 index 00000000..81dc96ea --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/auth/SessionRevocationConfig.java @@ -0,0 +1,19 @@ +package org.raddatz.familienarchiv.auth; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository; + +@Configuration +class SessionRevocationConfig { + + @Bean + SessionRevocationPort sessionRevocationPort( + @Autowired(required = false) JdbcIndexedSessionRepository sessionRepository) { + if (sessionRepository != null) { + return new JdbcSessionRevocationAdapter(sessionRepository); + } + return new NoOpSessionRevocationAdapter(); + } +} diff --git a/backend/src/test/java/org/raddatz/familienarchiv/auth/AuthSessionIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/auth/AuthSessionIntegrationTest.java index 1a8786a4..99a4dcb2 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/auth/AuthSessionIntegrationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/auth/AuthSessionIntegrationTest.java @@ -119,6 +119,21 @@ class AuthSessionIntegrationTest { assertThat(me.getStatusCode().value()).isEqualTo(401); } + // ─── Task: CSRF rejection at integration layer ──────────────────────────── + + @Test + void post_without_csrf_token_returns_403_CSRF_TOKEN_MISSING() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + // Deliberately omit XSRF-TOKEN cookie and X-XSRF-TOKEN header + ResponseEntity response = http.postForEntity( + baseUrl + "/api/auth/logout", + new HttpEntity<>("{}", headers), String.class); + + assertThat(response.getStatusCode().value()).isEqualTo(403); + assertThat(response.getBody()).contains("CSRF_TOKEN_MISSING"); + } + // ─── helpers ───────────────────────────────────────────────────────────── /**