From 6d5e522421779c0310c1b2f517e2eb67dbd1523f Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Fri, 16 Oct 2026 17:44:51 +0100 Subject: [PATCH] add toke cleanup --- .../repositories/TokenFamilyRepository.java | 20 +++++++++++++++++++ .../ddf/vodsystem/security/TokenService.java | 17 ++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java b/src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java index a0ec109..55000c1 100644 --- a/src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java +++ b/src/main/java/com/ddf/vodsystem/repositories/TokenFamilyRepository.java @@ -2,9 +2,29 @@ package com.ddf.vodsystem.repositories; import com.ddf.vodsystem.entities.TokenFamily; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.time.Instant; + @Repository public interface TokenFamilyRepository extends JpaRepository { + /** + * Deletes families that can no longer be refreshed: revoked, expired, or with every refresh + * token expired. Their refresh tokens are removed by the {@code ON DELETE CASCADE} foreign key. + * + * @return the number of families deleted + */ + @Modifying + @Query(""" + DELETE FROM TokenFamily f + WHERE f.revokedAt IS NOT NULL + OR f.expiresAt <= ?1 + OR NOT EXISTS ( + SELECT 1 FROM RefreshToken r + WHERE r.tokenFamily = f AND r.expiresAt > ?1 + ) + """) + int deleteDead(Instant now); } diff --git a/src/main/java/com/ddf/vodsystem/security/TokenService.java b/src/main/java/com/ddf/vodsystem/security/TokenService.java index 259e966..8d7e83c 100644 --- a/src/main/java/com/ddf/vodsystem/security/TokenService.java +++ b/src/main/java/com/ddf/vodsystem/security/TokenService.java @@ -7,6 +7,9 @@ import com.ddf.vodsystem.entities.User; import com.ddf.vodsystem.exceptions.NotAuthenticated; import com.ddf.vodsystem.repositories.RefreshTokenRepository; import com.ddf.vodsystem.repositories.TokenFamilyRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,6 +31,7 @@ import java.util.Optional; */ @Service public class TokenService { + private static final Logger logger = LoggerFactory.getLogger(TokenService.class); private static final SecureRandom RNG = new SecureRandom(); private static final HexFormat HEX = HexFormat.of(); @@ -192,6 +196,19 @@ public class TokenService { } } + /** + * Scheduled cleanup that deletes token families that can no longer be refreshed, along with all + * their refresh tokens. + *

+ * Runs hourly. Used tokens in live families are kept, since reuse detection needs them. + */ + @Scheduled(fixedDelay = 3_600_000) + @Transactional + public void deleteDeadSessions() { + int deleted = tokenFamilyRepository.deleteDead(Instant.now(clock)); + if (deleted > 0) logger.info("Deleted {} dead token families", deleted); + } + /** * Result of a successful {@link #rotate(String)}. *