fix(cognito): keep revoked tokens across restarts #5

Merged
Zakaria merged 1 commits from fix/cognito-revoked-tokens-reload into main 2026-10-24 18:53:00 +00:00
2 changed files with 51 additions and 0 deletions
@@ -3,13 +3,19 @@ package io.github.hectorvent.floci.services.cognito.model;
import io.quarkus.runtime.annotations.RegisterForReflection; import io.quarkus.runtime.annotations.RegisterForReflection;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* Information about a revoked token for tracking and validation purposes. * Information about a revoked token for tracking and validation purposes.
* Used to implement AWS Cognito AdminUserGlobalSignOut token revocation behavior. * Used to implement AWS Cognito AdminUserGlobalSignOut token revocation behavior.
*
* <p>{@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 @RegisterForReflection
@JsonIgnoreProperties(ignoreUnknown = true)
public class RevokedTokenInfo { public class RevokedTokenInfo {
private final String jti; private final String jti;
@@ -62,6 +68,7 @@ public class RevokedTokenInfo {
/** /**
* Check if this revocation record has expired and can be cleaned up. * Check if this revocation record has expired and can be cleaned up.
*/ */
@JsonIgnore
public boolean isExpired() { public boolean isExpired() {
return System.currentTimeMillis() / 1000L > expiresAt; return System.currentTimeMillis() / 1000L > expiresAt;
} }
@@ -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<Map<String, RevokedTokenInfo>> 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());
}
}