From 3d6577fe02469f10c46e84052cd1f28c514f7f13 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Tue, 20 Aug 2024 11:07:41 -0500 Subject: [PATCH] Added soft delete to RefreshTokenEntity to prevent deadlock and 500 errors. --- .../api/MealsMadeEasyApiApplication.java | 2 ++ .../api/auth/AuthServiceImpl.java | 18 +++++++++++++----- .../mealsmadeeasy/api/auth/RefreshToken.java | 1 + .../api/auth/RefreshTokenEntity.java | 14 +++++++++++++- .../api/auth/RefreshTokenRepository.java | 11 ++++++++--- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/main/java/app/mealsmadeeasy/api/MealsMadeEasyApiApplication.java b/src/main/java/app/mealsmadeeasy/api/MealsMadeEasyApiApplication.java index 0cc9264..8bead1c 100644 --- a/src/main/java/app/mealsmadeeasy/api/MealsMadeEasyApiApplication.java +++ b/src/main/java/app/mealsmadeeasy/api/MealsMadeEasyApiApplication.java @@ -2,8 +2,10 @@ package app.mealsmadeeasy.api; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class MealsMadeEasyApiApplication { public static void main(String[] args) { diff --git a/src/main/java/app/mealsmadeeasy/api/auth/AuthServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/auth/AuthServiceImpl.java index 106a51d..981fc3e 100644 --- a/src/main/java/app/mealsmadeeasy/api/auth/AuthServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/auth/AuthServiceImpl.java @@ -5,6 +5,7 @@ import app.mealsmadeeasy.api.user.UserEntity; import jakarta.transaction.Transactional; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -13,6 +14,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Service public class AuthServiceImpl implements AuthService { @@ -76,17 +78,18 @@ public class AuthServiceImpl implements AuthService { final RefreshTokenEntity old = this.refreshTokenRepository.findByToken(refreshToken) .orElseThrow(() -> new LoginException( LoginExceptionReason.INVALID_REFRESH_TOKEN, - "No such refresh-token: " + refreshToken + "No such refresh token: " + refreshToken )); - if (old.isRevoked()) { - throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "RefreshToken is revoked."); + if (old.isRevoked() || old.isDeleted()) { + throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "Invalid refresh token."); } if (old.getExpires().isBefore(LocalDateTime.now())) { - throw new LoginException(LoginExceptionReason.EXPIRED_REFRESH_TOKEN, "RefreshToken is expired."); + throw new LoginException(LoginExceptionReason.EXPIRED_REFRESH_TOKEN, "Refresh token is expired."); } final UserEntity principal = old.getOwner(); - this.refreshTokenRepository.delete(old); + old.setDeleted(true); + this.refreshTokenRepository.save(old); final String username = principal.getUsername(); return new LoginDetails( @@ -96,4 +99,9 @@ public class AuthServiceImpl implements AuthService { ); } + @Scheduled(fixedDelay = 60, timeUnit = TimeUnit.SECONDS) + public void cleanUpDeletedRefreshTokens() { + this.refreshTokenRepository.deleteAllWhereSoftDeleted(); + } + } diff --git a/src/main/java/app/mealsmadeeasy/api/auth/RefreshToken.java b/src/main/java/app/mealsmadeeasy/api/auth/RefreshToken.java index ddad1f7..a20ff1e 100644 --- a/src/main/java/app/mealsmadeeasy/api/auth/RefreshToken.java +++ b/src/main/java/app/mealsmadeeasy/api/auth/RefreshToken.java @@ -7,4 +7,5 @@ import java.time.LocalDateTime; public interface RefreshToken extends AuthToken { LocalDateTime getIssued(); boolean isRevoked(); + boolean isDeleted(); } diff --git a/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenEntity.java b/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenEntity.java index 30a3713..03d8527 100644 --- a/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenEntity.java +++ b/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenEntity.java @@ -30,6 +30,9 @@ public class RefreshTokenEntity implements RefreshToken { @ManyToOne private UserEntity owner; + @Column(nullable = false) + private Boolean deleted = false; + public Long getId() { return this.id; } @@ -70,7 +73,7 @@ public class RefreshTokenEntity implements RefreshToken { return this.revoked; } - public void setRevoked(Boolean revoked) { + public void setRevoked(boolean revoked) { this.revoked = revoked; } @@ -82,6 +85,15 @@ public class RefreshTokenEntity implements RefreshToken { this.owner = owner; } + @Override + public boolean isDeleted() { + return this.deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + @Override public long getLifetime() { return ChronoUnit.SECONDS.between(this.issued, this.expiration); diff --git a/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java b/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java index d90387c..71fe26d 100644 --- a/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/auth/RefreshTokenRepository.java @@ -1,14 +1,19 @@ package app.mealsmadeeasy.api.auth; -import jakarta.persistence.LockModeType; +import jakarta.transaction.Transactional; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; public interface RefreshTokenRepository extends JpaRepository { - @Lock(LockModeType.PESSIMISTIC_READ) Optional findByToken(String token); + @Modifying + @Transactional + @Query("DELETE FROM RefreshToken t WHERE t.deleted = true") + void deleteAllWhereSoftDeleted(); + }