add lock to read by Hash
All checks were successful
CI / Maven unit tests (push) Successful in 15s
CI / Build and push images (push) Has been skipped
CI / Deploy to server (push) Has been skipped

This commit is contained in:
2026-10-07 18:31:47 +01:00
parent 11115a435a
commit 40fc80537b
2 changed files with 11 additions and 14 deletions

View File

@@ -1,15 +1,17 @@
package com.ddf.vodsystem.repositories; package com.ddf.vodsystem.repositories;
import com.ddf.vodsystem.entities.RefreshToken; import com.ddf.vodsystem.entities.RefreshToken;
import jakarta.persistence.LockModeType;
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.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.sql.Ref;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> { public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT r FROM RefreshToken r WHERE r.tokenHash = ?1") @Query("SELECT r FROM RefreshToken r WHERE r.tokenHash = ?1")
Optional<RefreshToken> findByHash(byte[] tokenHash); Optional<RefreshToken> findByHash(byte[] tokenHash);
} }

View File

@@ -16,7 +16,6 @@ import java.security.SecureRandom;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.HexFormat; import java.util.HexFormat;
import java.util.Optional;
@Service @Service
public class TokenService { public class TokenService {
@@ -83,21 +82,17 @@ public class TokenService {
.orElseThrow(() -> new NotAuthenticated("No such refresh token")); .orElseThrow(() -> new NotAuthenticated("No such refresh token"));
// Find Token Family // Find Token Family
Optional<TokenFamily> tokenFamily = tokenFamilyRepository.findById(refreshToken.getTokenFamily().getId()); TokenFamily tokenFamily = refreshToken.getTokenFamily();
if (tokenFamily.isEmpty()) { if (tokenFamily.getRevokedAt() != null) {
throw new IllegalStateException("Token must be tied to a family");
}
if (tokenFamily.get().getRevokedAt() != null) {
throw new NotAuthenticated("Token family has been revoked"); throw new NotAuthenticated("Token family has been revoked");
} }
// Reuse Check // Reuse Check
if (refreshToken.getUsedAt() != null) { if (refreshToken.getUsedAt() != null) {
if (now.isAfter(refreshToken.getUsedAt().plus(refreshGracePeriod))) { if (now.isAfter(refreshToken.getUsedAt().plus(refreshGracePeriod))) {
tokenFamily.get().setRevokedAt(Instant.now()); tokenFamily.setRevokedAt(Instant.now());
tokenFamilyRepository.save(tokenFamily.get()); tokenFamilyRepository.save(tokenFamily);
} }
throw new NotAuthenticated("Refresh token already being used"); throw new NotAuthenticated("Refresh token already being used");
@@ -106,7 +101,7 @@ public class TokenService {
// Expiry Check // Expiry Check
if ( if (
now.isAfter(refreshToken.getExpiresAt()) || now.isAfter(refreshToken.getExpiresAt()) ||
now.isAfter(tokenFamily.get().getExpiresAt()) now.isAfter(tokenFamily.getExpiresAt())
) { ) {
throw new NotAuthenticated("Refresh token or family expired"); throw new NotAuthenticated("Refresh token or family expired");
} }
@@ -118,10 +113,10 @@ public class TokenService {
byte[] newRawToken = generateRandomBytes(32); byte[] newRawToken = generateRandomBytes(32);
createRefreshToken( createRefreshToken(
newRawToken, newRawToken,
tokenFamily.get() tokenFamily
); );
return new Rotation(tokenFamily.get().getUser().getId(), HEX.formatHex(newRawToken)); return new Rotation(tokenFamily.getUser().getId(), HEX.formatHex(newRawToken));
} }
public record Rotation(Long userId, String newRefreshToken) {} public record Rotation(Long userId, String newRefreshToken) {}
@@ -152,7 +147,7 @@ public class TokenService {
refreshToken.setTokenHash(sha256(token)); refreshToken.setTokenHash(sha256(token));
refreshToken.setCreatedAt(now); refreshToken.setCreatedAt(now);
refreshToken.setExpiresAt(now.plusMillis(refreshTokenExpirationMs.toMillis())); refreshToken.setExpiresAt(now.plusMillis(refreshTokenExpirationMs.toMillis()));
return refreshTokenRepository.saveAndFlush(refreshToken); return refreshTokenRepository.save(refreshToken);
} }
} }