Added soft delete to RefreshTokenEntity to prevent deadlock and 500 errors.
This commit is contained in:
		
							parent
							
								
									0396e8e3b0
								
							
						
					
					
						commit
						3d6577fe02
					
				| @ -2,8 +2,10 @@ package app.mealsmadeeasy.api; | |||||||
| 
 | 
 | ||||||
| import org.springframework.boot.SpringApplication; | import org.springframework.boot.SpringApplication; | ||||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||||
|  | import org.springframework.scheduling.annotation.EnableScheduling; | ||||||
| 
 | 
 | ||||||
| @SpringBootApplication | @SpringBootApplication | ||||||
|  | @EnableScheduling | ||||||
| public class MealsMadeEasyApiApplication { | public class MealsMadeEasyApiApplication { | ||||||
| 
 | 
 | ||||||
| 	public static void main(String[] args) { | 	public static void main(String[] args) { | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import app.mealsmadeeasy.api.user.UserEntity; | |||||||
| import jakarta.transaction.Transactional; | import jakarta.transaction.Transactional; | ||||||
| import org.jetbrains.annotations.Nullable; | import org.jetbrains.annotations.Nullable; | ||||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||||
|  | import org.springframework.scheduling.annotation.Scheduled; | ||||||
| import org.springframework.security.authentication.AuthenticationManager; | import org.springframework.security.authentication.AuthenticationManager; | ||||||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||||
| import org.springframework.security.core.Authentication; | import org.springframework.security.core.Authentication; | ||||||
| @ -13,6 +14,7 @@ import org.springframework.stereotype.Service; | |||||||
| 
 | 
 | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
| 
 | 
 | ||||||
| @Service | @Service | ||||||
| public class AuthServiceImpl implements AuthService { | public class AuthServiceImpl implements AuthService { | ||||||
| @ -76,17 +78,18 @@ public class AuthServiceImpl implements AuthService { | |||||||
|         final RefreshTokenEntity old = this.refreshTokenRepository.findByToken(refreshToken) |         final RefreshTokenEntity old = this.refreshTokenRepository.findByToken(refreshToken) | ||||||
|                 .orElseThrow(() -> new LoginException( |                 .orElseThrow(() -> new LoginException( | ||||||
|                         LoginExceptionReason.INVALID_REFRESH_TOKEN, |                         LoginExceptionReason.INVALID_REFRESH_TOKEN, | ||||||
|                         "No such refresh-token: " + refreshToken |                         "No such refresh token: " + refreshToken | ||||||
|                 )); |                 )); | ||||||
|         if (old.isRevoked()) { |         if (old.isRevoked() || old.isDeleted()) { | ||||||
|             throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "RefreshToken is revoked."); |             throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "Invalid refresh token."); | ||||||
|         } |         } | ||||||
|         if (old.getExpires().isBefore(LocalDateTime.now())) { |         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(); |         final UserEntity principal = old.getOwner(); | ||||||
|         this.refreshTokenRepository.delete(old); |         old.setDeleted(true); | ||||||
|  |         this.refreshTokenRepository.save(old); | ||||||
| 
 | 
 | ||||||
|         final String username = principal.getUsername(); |         final String username = principal.getUsername(); | ||||||
|         return new LoginDetails( |         return new LoginDetails( | ||||||
| @ -96,4 +99,9 @@ public class AuthServiceImpl implements AuthService { | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Scheduled(fixedDelay = 60, timeUnit = TimeUnit.SECONDS) | ||||||
|  |     public void cleanUpDeletedRefreshTokens() { | ||||||
|  |         this.refreshTokenRepository.deleteAllWhereSoftDeleted(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,4 +7,5 @@ import java.time.LocalDateTime; | |||||||
| public interface RefreshToken extends AuthToken { | public interface RefreshToken extends AuthToken { | ||||||
|     LocalDateTime getIssued(); |     LocalDateTime getIssued(); | ||||||
|     boolean isRevoked(); |     boolean isRevoked(); | ||||||
|  |     boolean isDeleted(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -30,6 +30,9 @@ public class RefreshTokenEntity implements RefreshToken { | |||||||
|     @ManyToOne |     @ManyToOne | ||||||
|     private UserEntity owner; |     private UserEntity owner; | ||||||
| 
 | 
 | ||||||
|  |     @Column(nullable = false) | ||||||
|  |     private Boolean deleted = false; | ||||||
|  | 
 | ||||||
|     public Long getId() { |     public Long getId() { | ||||||
|         return this.id; |         return this.id; | ||||||
|     } |     } | ||||||
| @ -70,7 +73,7 @@ public class RefreshTokenEntity implements RefreshToken { | |||||||
|         return this.revoked; |         return this.revoked; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setRevoked(Boolean revoked) { |     public void setRevoked(boolean revoked) { | ||||||
|         this.revoked = revoked; |         this.revoked = revoked; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -82,6 +85,15 @@ public class RefreshTokenEntity implements RefreshToken { | |||||||
|         this.owner = owner; |         this.owner = owner; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean isDeleted() { | ||||||
|  |         return this.deleted; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setDeleted(boolean deleted) { | ||||||
|  |         this.deleted = deleted; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public long getLifetime() { |     public long getLifetime() { | ||||||
|         return ChronoUnit.SECONDS.between(this.issued, this.expiration); |         return ChronoUnit.SECONDS.between(this.issued, this.expiration); | ||||||
|  | |||||||
| @ -1,14 +1,19 @@ | |||||||
| package app.mealsmadeeasy.api.auth; | 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.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; | import java.util.Optional; | ||||||
| 
 | 
 | ||||||
| public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long> { | public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long> { | ||||||
| 
 | 
 | ||||||
|     @Lock(LockModeType.PESSIMISTIC_READ) |  | ||||||
|     Optional<RefreshTokenEntity> findByToken(String token); |     Optional<RefreshTokenEntity> findByToken(String token); | ||||||
| 
 | 
 | ||||||
|  |     @Modifying | ||||||
|  |     @Transactional | ||||||
|  |     @Query("DELETE FROM RefreshToken t WHERE t.deleted = true") | ||||||
|  |     void deleteAllWhereSoftDeleted(); | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Jesse Brault
						Jesse Brault