fix(cognito): keep revoked tokens across restarts #5
@@ -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.
|
||||
*
|
||||
* <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
|
||||
@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;
|
||||
}
|
||||
|
||||
+44
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user