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.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.scheduling.annotation.EnableScheduling; | ||||
| 
 | ||||
| @SpringBootApplication | ||||
| @EnableScheduling | ||||
| public class MealsMadeEasyApiApplication { | ||||
| 
 | ||||
| 	public static void main(String[] args) { | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -7,4 +7,5 @@ import java.time.LocalDateTime; | ||||
| public interface RefreshToken extends AuthToken { | ||||
|     LocalDateTime getIssued(); | ||||
|     boolean isRevoked(); | ||||
|     boolean isDeleted(); | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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<RefreshTokenEntity, Long> { | ||||
| 
 | ||||
|     @Lock(LockModeType.PESSIMISTIC_READ) | ||||
|     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