From 73af11e84bcfed61e2b35249573cc264568fbea2 Mon Sep 17 00:00:00 2001 From: Marcel Raddatz Date: Fri, 10 Apr 2026 22:22:54 +0200 Subject: [PATCH] fix(invite): reject invalidated invites in acceptInvite Same invalidatedAt gap as getInviteInfo: a superseded invite (status still 'pending', invalidatedAt set) could still be used to create an account and join the household. Co-Authored-By: Claude Sonnet 4.6 --- .../com/recipeapp/household/HouseholdService.java | 4 +++- .../recipeapp/household/HouseholdServiceTest.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/recipeapp/household/HouseholdService.java b/backend/src/main/java/com/recipeapp/household/HouseholdService.java index 74ce768..e72a9d4 100644 --- a/backend/src/main/java/com/recipeapp/household/HouseholdService.java +++ b/backend/src/main/java/com/recipeapp/household/HouseholdService.java @@ -205,7 +205,9 @@ public class HouseholdService { HouseholdInvite invite = householdInviteRepository.findByInviteCode(code) .orElseThrow(() -> new ResourceNotFoundException("Invite not found or invalid")); - if ("used".equals(invite.getStatus()) || invite.getExpiresAt().isBefore(Instant.now())) { + if ("used".equals(invite.getStatus()) + || invite.getInvalidatedAt() != null + || invite.getExpiresAt().isBefore(Instant.now())) { throw new ResourceNotFoundException("Invite not found or invalid"); } diff --git a/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java b/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java index 1c9cc96..223d5cd 100644 --- a/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java +++ b/backend/src/test/java/com/recipeapp/household/HouseholdServiceTest.java @@ -288,6 +288,20 @@ class HouseholdServiceTest { .isInstanceOf(ResourceNotFoundException.class); } + @Test + void acceptInviteShouldThrow404WhenInviteIsInvalidated() { + var owner = testUser(); + var household = new Household("Smith family", owner); + var invite = new HouseholdInvite(household, "SUPERSEDED", Instant.now().plusSeconds(86400)); + invite.setInvalidatedAt(Instant.now()); // superseded by a new invite + + when(userAccountRepository.existsByEmailIgnoreCase("tom@example.com")).thenReturn(false); + when(householdInviteRepository.findByInviteCode("SUPERSEDED")).thenReturn(Optional.of(invite)); + + assertThatThrownBy(() -> householdService.acceptInvite("SUPERSEDED", "Tom", "tom@example.com", "secret123")) + .isInstanceOf(ResourceNotFoundException.class); + } + @Test void createHouseholdShouldThrowWhenUserNotFound() { when(userAccountRepository.findByEmailIgnoreCase("unknown@example.com")).thenReturn(Optional.empty());