diff --git a/src/main/java/io/github/hectorvent/floci/services/cognito/model/RevokedTokenInfo.java b/src/main/java/io/github/hectorvent/floci/services/cognito/model/RevokedTokenInfo.java index ac77da8..2942aa4 100644 --- a/src/main/java/io/github/hectorvent/floci/services/cognito/model/RevokedTokenInfo.java +++ b/src/main/java/io/github/hectorvent/floci/services/cognito/model/RevokedTokenInfo.java @@ -3,13 +3,19 @@ package io.github.hectorvent.floci.services.cognito.model; import io.quarkus.runtime.annotations.RegisterForReflection; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** * Information about a revoked token for tracking and validation purposes. * Used to implement AWS Cognito AdminUserGlobalSignOut token revocation behavior. + * + *

{@code expired} is derived from {@code expiresAt}, so it is not stored. Files written before + * that still carry it, and are read with it ignored instead of being discarded as corrupt. */ @RegisterForReflection +@JsonIgnoreProperties(ignoreUnknown = true) public class RevokedTokenInfo { private final String jti; @@ -62,6 +68,7 @@ public class RevokedTokenInfo { /** * Check if this revocation record has expired and can be cleaned up. */ + @JsonIgnore public boolean isExpired() { return System.currentTimeMillis() / 1000L > expiresAt; } diff --git a/src/test/java/io/github/hectorvent/floci/services/cognito/RevokedTokenInfoPersistenceTest.java b/src/test/java/io/github/hectorvent/floci/services/cognito/RevokedTokenInfoPersistenceTest.java new file mode 100644 index 0000000..0a3c263 --- /dev/null +++ b/src/test/java/io/github/hectorvent/floci/services/cognito/RevokedTokenInfoPersistenceTest.java @@ -0,0 +1,44 @@ +package io.github.hectorvent.floci.services.cognito; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.hectorvent.floci.services.cognito.model.RevokedTokenInfo; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** Revoked tokens must survive a restart, which reads them back with a strict ObjectMapper. */ +class RevokedTokenInfoPersistenceTest { + + private static final TypeReference> TYPE = new TypeReference<>() { + }; + private final ObjectMapper strictMapper = new ObjectMapper(); + + @Test + void roundTripsWithoutTheDerivedExpiredField() throws Exception { + RevokedTokenInfo info = new RevokedTokenInfo("jti-1", "refresh", "alice", "us-east-1_pool", 1_000L, 4_102_444_800L); + + String json = strictMapper.writeValueAsString(Map.of("k", info)); + RevokedTokenInfo read = strictMapper.readValue(json, TYPE).get("k"); + + assertFalse(json.contains("\"expired\""), json); + assertEquals("jti-1", read.getJti()); + assertEquals(4_102_444_800L, read.getExpiresAt()); + } + + @Test + void readsFilesWrittenWithTheExpiredField() throws Exception { + String saved = """ + {"000000000000/revoked:us-east-1_pool:jti-1":{"jti":"jti-1","tokenType":"refresh","username":"alice", + "userPoolId":"us-east-1_pool","revokedAt":1792858112669,"expiresAt":1824394112,"expired":false}} + """; + + RevokedTokenInfo read = strictMapper.readValue(saved, TYPE).get("000000000000/revoked:us-east-1_pool:jti-1"); + + assertEquals("alice", read.getUsername()); + assertEquals(1824394112L, read.getExpiresAt()); + } +}